MH8-Music-Prompt-Pro: A Single-File Protocol for Deterministic Provenance in AI Music Prompting
Technical White Paper
Version 1.0.0
Published: February 16, 2026
Author: ACBEATZ.COM (MH8 Research Lab)
DOI: [https://zenodo.org/uploads/18665540]
[https://github.com/acbeatz]
[https://acbeatz.com]

Abstract
This paper documents MH8-Music-Prompt-Pro, a single-file, dependency-free Single Page Application (SPA) that implements a deterministic provenance protocol for AI music generation prompts. The system introduces six novel mechanisms:

Triple-layer proof-of-creation receipts with embedded SHA-256 cryptographic anchors

Canonical JSON serialization for creative artifact hashing

Continuity-lock mechanism for musical parameter provenance (GENRE/BPM/KEY_SIGNATURE)

Multi-portal structured prompting schema optimized for Suno/Udio platforms

Bracketed hook-pack ingestion and injection pipeline

User-embedded SHA256_ID serialization bridging human tracking with machine verification

The protocol ensures reproducible, timestamped, auditable prompt provenance from human creation through AI generation, addressing a critical gap in AI music workflow transparency.

Keywords: AI music provenance, deterministic hashing, structured prompting, continuity lock, creative artifact serialization, single-file SPA

1. Introduction
AI music platforms (Suno, Udio) accept unstructured prompts combining lyrics, style tags, and production directives. No existing system provides:

Cryptographic proof-of-origin for the prompt itself

Parameter continuity tracking (GENRE/BPM/KEY locking)

Structured multi-layer serialization separating lyrics from metadata

Hook ecosystem extensibility for reusable prompt components

Single-file deployment across all environments

MH8-Music-Prompt-Pro solves these gaps through a protocol-first UI that generates triple-layer receipts:

text
Layer 1: LYRICS → Platform lyrics field
Layer 2: STYLE/TAGS/SETTINGS → Platform prompt field  
Layer 3: HASHED_RECEIPT → Private cryptographic proof
Each receipt contains a deterministic SHA-256 hash of canonical JSON payload:

text
hash = SHA256("MH8-Acbeatz.com|" + stableStringify(core_payload))
2. Protocol Architecture
2.1 Multi-Portal Schema
The system defines 22 canonical portals representing the complete AI music prompt surface area:

text
CORE: MODEL_BEHAVIOR_CONTROL, LYRICS, GENRE, MOOD, ENERGY, VOCAL_INTENSITY
PARAMETERS: ERA_INFLUENCE, BPM, KEY_SIGNATURE
INSTRUMENTS: BACKING_VOX, DRUMS, GUITAR, BASS, PIANO_KEYS, SYNTH, PERCUSSION
PRODUCTION: DYNAMICS, MIX, MASTER
EXTENSION: EXTRA_HOOKS, NOTES, CONTINUITY_LOCK, SHA256_ID
Three tiered flows expose portal subsets:

QUICK: 7 portals (behavior, lyrics, core style, tracking)

SEMI: 19 portals (full production stack)

ADVANCED: 22 portals (complete surface area)

2.2 Triple-Layer Receipt Model
text
LAYER 1 (Platform-ready):
[LAYER 1 — LYRICS (PASTE THIS)]
{raw lyrics text}

LAYER 2 (Platform-ready):  
[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]
• [PLATFORM] Suno
• [GENRE] rock
• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY: A
• [SHA256_ID] b9ac803080cc52ad0c7f09594240f578421f86cddbc49561954a74d2ed2ed39a
[SHA 256 ID + {minted_hash_hex}]

LAYER 3 (Cryptographic proof):
machine_hard: {
  core_payload: {portals, continuity_lock, platform, timestamp},
  canonical_payload: "{frozen JSON string}",
  hash_input: "MH8-Acbeatz.com|{canonical_payload}",
  sha256_hex: "{deterministic hash}",
  cryptographic_receipt_status: "VALID"
}
2.3 Canonical Hashing Pipeline
text
1. core_payload = {
     system: "MH8-Music-Prompt-Pro",
     version: "1.0.0", 
     brand: "ACBEATZ.COM",
     platform: "Suno",
     prompt_name: "Prompt 1",
     created_utc: "2026-02-16T16:15:00Z",
     portals: {LYRICS: "...", GENRE: "...", ...},
     continuity_lock: {engaged: true, genre: "rock", bpm: 88, key_signature: "A"},
     imported_hooks_count: 200
   }

2. canonical = stableStringify(core_payload)  // deterministic JSON

3. hash_input = "MH8-Acbeatz.com|" + canonical

4. minted_hash = SHA256(hash_input)  // crypto.subtle.digest('SHA-256')
Key innovation: The minted SHA-256 hash is serialized back into Layer 2 as the final line, creating a closed verification loop:

text
Prompt → AI Platform → Track → Hash matches Layer 3 → Provenance confirmed
3. Continuity Lock Mechanism
Novel contribution: Parameterized continuity tracking for musical elements.

text
1. DISENGAGED: Normal editing of GENRE/BPM/KEY_SIGNATURE portals
2. ADD_LOCK: Snapshot current portal values → {engaged: true, genre: X, bpm: Y, key: Z}
3. ENGAGE: Lock portals read-only, apply snapshot values, visual LOCKED state
4. Locked portals reject edits, toast "Continuity lock active", re-apply values
Receipt serialization:

text
[CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A
Verification: Any observer can confirm the track's GENRE/BPM/KEY matches the locked snapshot at prompt creation time.

4. Bracketed Hook-Pack Ingestion
Protocol: JSON files containing bracketed strings ([Performance: Emotion | choose 1 word | commit]) are:

Parsed: Bracketed regex extraction → flat array of hooks

Merged: Union of all imported packs → state.hooks[]

Filtered: Pack selector chips toggle between "MERGED" vs individual packs

Injected: Click-to-append into any portal via modal editor

Scalability: Infinite hook packs, deterministic merging, pack-level filtering preserves provenance.

5. Single-File SPA Implementation
Architecture density (97kb minified):

text
- 2000+ lines vanilla HTML/CSS/JS
- CSS custom properties + light/dark theming
- ARIA-complete accessibility (tablist, dialog, status)
- Keyboard navigation (←→ Enter, tab cycling)
- Clipboard API + legacy textarea fallback
- crypto.subtle SHA256 + Uint8Array polyfill
- localStorage vault persistence
- Mobile viewport-fit=cover hardening
No dependencies: Ships as one HTML file. Runs in any modern browser.

6. Prompt Entries Completed (Verification Surface)
Critical UX/protocol bridge: Button exposes Layer 1 + Layer 2 only with:

text
[PROMPT ENTRIES COMPLETED — COPY/PASTE]
INSTRUCTIONS: Copy/paste the two blocks below into your AI music platform.

[LAYER 1 — LYRICS (PASTE THIS)]
{lyrics}

[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]
• [PLATFORM] CUSTOM
• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A
• [SHA256_ID] {user_tracking_id}
[SHA 256 ID + {minted_sha256_hex}]
The final line ([SHA 256 ID + {minted_hash}]) embeds machine-readable verification directly in the human-facing prompt.

7. Verification Workflow
Given a Suno/Udio track with embedded SHA-256:

text
1. Extract Layer 2 final line → minted_hash = "b513cff2c553aeb8..."
2. Reconstruct core_payload from Layer 1+2+user SHA256_ID
3. canonical = stableStringify(core_payload)
4. hash_input = "MH8-Acbeatz.com|" + canonical  
5. computed_hash = SHA256(hash_input)
6. IF computed_hash == minted_hash → PROVENANCE VERIFIED
8. Related Work & Contributions
Existing systems lack:

Cryptographic prompt provenance

Continuity locking for musical parameters

Structured multi-layer prompt serialization

Hook ecosystem extensibility

Deterministic canonicalization rules

Novel contributions:

First deterministic provenance protocol for AI music prompts

Continuity-lock mechanism for musical parameter integrity

Triple-layer receipt model bridging human/AI/machine verification

Bracketed hook-pack standard for prompt component reusability

Single-file SPA reference implementation (97kb, zero dependencies)

9. Implementation Availability
text
GitHub: github.com/acbeatz/mh8-music-prompt-pro
Zenodo: [DOI pending]
Single-file SPA: MH8-Music-Prompt-Pro-v1.0.0.html (97kb)
Schema: PORTAL_KEYS.json (canonical portal definitions)
Receipt spec: TRIPLE_LAYER_RECEIPT_v1.md
10. Future Work
Multi-agent verification: Third-party receipt validation service

Blockchain anchoring: Periodic SHA-256 batching to public ledger

Protocol v2: Vector embeddings of hook packs for semantic search

Cross-platform expansion: Stable Audio, MusicGen, Riffusion

Formal verification: Coq/HOL proofs of canonicalization determinism

Acknowledgments
Built atop MH8-R-R fact-checking protocol research. Deployed as ACBEATZ.COM production tool.

Copyright © 2026 ACBEATZ.COM
License: CC-BY-4.0 (attribution required for academic use)
Contact: research@acbeatz.com

This paper documents protocol mechanisms, not marketing claims. All specifications are verifiable from the reference implementation. Citations should reference the DOI and GitHub implementation.

End of white paper.


||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[Sealed white paper + mh8 music prompt pro code sha256 2-16-2026]

PASS ✅
Brand: ACBEATZ.COM
Claimed sha256_hex: 54b73e2123b6c91e22ad4825f07ad29187e330c57de1457aea50ccf471a05f6e
Computed sha256_hex: 54b73e2123b6c91e22ad4825f07ad29187e330c57de1457aea50ccf471a05f6e
hash_input_bytes: 9304 | LF=0 CRLF=0 CR=0 | endsWithNewline=NO
hash_input first: ACBEATZ.COM|{"artifact":{"core_entry":"[https://zenodo.org/uploads/18665540][htt
hash_input last: eipt_type":"MH8-PROTOCOL-HUB-CORE-MINT","receipt_version":"PROTOCOL_HUB_UI_V13"}
{
  "human_pretty": "ACBEATZ.COM — MH8 Protocol Hub\nReceipt: Core Entry Mint (dual-layer)\nCore Entry: [https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\nhttps://github.com/acbeatz\nhttps://acbeatz.com/n-eyes\nhttps://orcid.org/0009-0003-3846-9082]\n\nMH8-Music-Prompt-Pro: A Single-File Protocol for Deterministic Provenance in AI Music Prompting\nTechnical White Paper\nVersion 1.0.0\nPublished: February 16, 2026\nAuthor: ACBEATZ.COM (MH8 Research Lab)\nDOI: [To be assigned upon Zenodo upload]\n\nAbstract\nThis paper documents MH8-Music-Prompt-Pro, a single-file, dependency-free Single Page Application (SPA) that implements a deterministic provenance protocol for AI music generation prompts. The system introduces six novel mechanisms:\n\nTriple-layer proof-of-creation receipts with embedded SHA-256 cryptographic anchors\n\nCanonical JSON serialization for creative artifact hashing\n\nContinuity-lock mechanism for musical parameter provenance (GENRE/BPM/KEY_SIGNATURE)\n\nMulti-portal structured prompting schema optimized for Suno/Udio platforms\n\nBracketed hook-pack ingestion and injection pipeline\n\nUser-embedded SHA256_ID serialization bridging human tracking with machine verification\n\nThe protocol ensures reproducible, timestamped, auditable prompt provenance from human creation through AI generation, addressing a critical gap in AI music workflow transparency.\n\nKeywords: AI music provenance, deterministic hashing, structured prompting, continuity lock, creative artifact serialization, single-file SPA\n\n1. Introduction\nAI music platforms (Suno, Udio) accept unstructured prompts combining lyrics, style tags, and production directives. No existing system provides:\n\nCryptographic proof-of-origin for the prompt itself\n\nParameter continuity tracking (GENRE/BPM/KEY locking)\n\nStructured multi-layer serialization separating lyrics from metadata\n\nHook ecosystem extensibility for reusable prompt components\n\nSingle-file deployment across all environments\n\nMH8-Music-Prompt-Pro solves these gaps through a protocol-first UI that generates triple-layer receipts:\n\ntext\nLayer 1: LYRICS → Platform lyrics field\nLayer 2: STYLE/TAGS/SETTINGS → Platform prompt field  \nLayer 3: HASHED_RECEIPT → Private cryptographic proof\nEach receipt contains a deterministic SHA-256 hash of canonical JSON payload:\n\ntext\nhash = SHA256(\"MH8-Acbeatz.com|\" + stableStringify(core_payload))\n2. Protocol Architecture\n2.1 Multi-Portal Schema\nThe system defines 22 canonical portals representing the complete AI music prompt surface area:\n\ntext\nCORE: MODEL_BEHAVIOR_CONTROL, LYRICS, GENRE, MOOD, ENERGY, VOCAL_INTENSITY\nPARAMETERS: ERA_INFLUENCE, BPM, KEY_SIGNATURE\nINSTRUMENTS: BACKING_VOX, DRUMS, GUITAR, BASS, PIANO_KEYS, SYNTH, PERCUSSION\nPRODUCTION: DYNAMICS, MIX, MASTER\nEXTENSION: EXTRA_HOOKS, NOTES, CONTINUITY_LOCK, SHA256_ID\nThree tiered flows expose portal subsets:\n\nQUICK: 7 portals (behavior, lyrics, core style, tracking)\n\nSEMI: 19 portals (full production stack)\n\nADVANCED: 22 portals (complete surface area)\n\n2.2 Triple-Layer Receipt Model\ntext\nLAYER 1 (Platform-ready):\n[LAYER 1 — LYRICS (PASTE THIS)]\n{raw lyrics text}\n\nLAYER 2 (Platform-ready):  \n[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\n• [PLATFORM] Suno\n• [GENRE] rock\n• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY: A\n• [SHA256_ID] b9ac803080cc52ad0c7f09594240f578421f86cddbc49561954a74d2ed2ed39a\n[SHA 256 ID + {minted_hash_hex}]\n\nLAYER 3 (Cryptographic proof):\nmachine_hard: {\n  core_payload: {portals, continuity_lock, platform, timestamp},\n  canonical_payload: \"{frozen JSON string}\",\n  hash_input: \"MH8-Acbeatz.com|{canonical_payload}\",\n  sha256_hex: \"{deterministic hash}\",\n  cryptographic_receipt_status: \"VALID\"\n}\n2.3 Canonical Hashing Pipeline\ntext\n1. core_payload = {\n     system: \"MH8-Music-Prompt-Pro\",\n     version: \"1.0.0\", \n     brand: \"ACBEATZ.COM\",\n     platform: \"Suno\",\n     prompt_name: \"Prompt 1\",\n     created_utc: \"2026-02-16T16:15:00Z\",\n     portals: {LYRICS: \"...\", GENRE: \"...\", ...},\n     continuity_lock: {engaged: true, genre: \"rock\", bpm: 88, key_signature: \"A\"},\n     imported_hooks_count: 200\n   }\n\n2. canonical = stableStringify(core_payload)  // deterministic JSON\n\n3. hash_input = \"MH8-Acbeatz.com|\" + canonical\n\n4. minted_hash = SHA256(hash_input)  // crypto.subtle.digest('SHA-256')\nKey innovation: The minted SHA-256 hash is serialized back into Layer 2 as the final line, creating a closed verification loop:\n\ntext\nPrompt → AI Platform → Track → Hash matches Layer 3 → Provenance confirmed\n3. Continuity Lock Mechanism\nNovel contribution: Parameterized continuity tracking for musical elements.\n\ntext\n1. DISENGAGED: Normal editing of GENRE/BPM/KEY_SIGNATURE portals\n2. ADD_LOCK: Snapshot current portal values → {engaged: true, genre: X, bpm: Y, key: Z}\n3. ENGAGE: Lock portals read-only, apply snapshot values, visual LOCKED state\n4. Locked portals reject edits, toast \"Continuity lock active\", re-apply values\nReceipt serialization:\n\ntext\n[CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A\nVerification: Any observer can confirm the track's GENRE/BPM/KEY matches the locked snapshot at prompt creation time.\n\n4. Bracketed Hook-Pack Ingestion\nProtocol: JSON files containing bracketed strings ([Performance: Emotion | choose 1 word | commit]) are:\n\nParsed: Bracketed regex extraction → flat array of hooks\n\nMerged: Union of all imported packs → state.hooks[]\n\nFiltered: Pack selector chips toggle between \"MERGED\" vs individual packs\n\nInjected: Click-to-append into any portal via modal editor\n\nScalability: Infinite hook packs, deterministic merging, pack-level filtering preserves provenance.\n\n5. Single-File SPA Implementation\nArchitecture density (97kb minified):\n\ntext\n- 2000+ lines vanilla HTML/CSS/JS\n- CSS custom properties + light/dark theming\n- ARIA-complete accessibility (tablist, dialog, status)\n- Keyboard navigation (←→ Enter, tab cycling)\n- Clipboard API + legacy textarea fallback\n- crypto.subtle SHA256 + Uint8Array polyfill\n- localStorage vault persistence\n- Mobile viewport-fit=cover hardening\nNo dependencies: Ships as one HTML file. Runs in any modern browser.\n\n6. Prompt Entries Completed (Verification Surface)\nCritical UX/protocol bridge: Button exposes Layer 1 + Layer 2 only with:\n\ntext\n[PROMPT ENTRIES COMPLETED — COPY/PASTE]\nINSTRUCTIONS: Copy/paste the two blocks below into your AI music platform.\n\n[LAYER 1 — LYRICS (PASTE THIS)]\n{lyrics}\n\n[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\n• [PLATFORM] CUSTOM\n• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A\n• [SHA256_ID] {user_tracking_id}\n[SHA 256 ID + {minted_sha256_hex}]\nThe final line ([SHA 256 ID + {minted_hash}]) embeds machine-readable verification directly in the human-facing prompt.\n\n7. Verification Workflow\nGiven a Suno/Udio track with embedded SHA-256:\n\ntext\n1. Extract Layer 2 final line → minted_hash = \"b513cff2c553aeb8...\"\n2. Reconstruct core_payload from Layer 1+2+user SHA256_ID\n3. canonical = stableStringify(core_payload)\n4. hash_input = \"MH8-Acbeatz.com|\" + canonical  \n5. computed_hash = SHA256(hash_input)\n6. IF computed_hash == minted_hash → PROVENANCE VERIFIED\n8. Related Work & Contributions\nExisting systems lack:\n\nCryptographic prompt provenance\n\nContinuity locking for musical parameters\n\nStructured multi-layer prompt serialization\n\nHook ecosystem extensibility\n\nDeterministic canonicalization rules\n\nNovel contributions:\n\nFirst deterministic provenance protocol for AI music prompts\n\nContinuity-lock mechanism for musical parameter integrity\n\nTriple-layer receipt model bridging human/AI/machine verification\n\nBracketed hook-pack standard for prompt component reusability\n\nSingle-file SPA reference implementation (97kb, zero dependencies)\n\n9. Implementation Availability\ntext\nGitHub: github.com/acbeatz/mh8-music-prompt-pro\nZenodo: [DOI pending]\nSingle-file SPA: MH8-Music-Prompt-Pro-v1.0.0.html (97kb)\nSchema: PORTAL_KEYS.json (canonical portal definitions)\nReceipt spec: TRIPLE_LAYER_RECEIPT_v1.md\n10. Future Work\nMulti-agent verification: Third-party receipt validation service\n\nBlockchain anchoring: Periodic SHA-256 batching to public ledger\n\nProtocol v2: Vector embeddings of hook packs for semantic search\n\nCross-platform expansion: Stable Audio, MusicGen, Riffusion\n\nFormal verification: Coq/HOL proofs of canonicalization determinism\n\nAcknowledgments\nBuilt atop MH8-R-R fact-checking protocol research. Deployed as ACBEATZ.COM production tool.\n\nCopyright © 2026 ACBEATZ.COM\nLicense: CC-BY-4.0 (attribution required for academic use)\nContact: research@acbeatz.com\n\nThis paper documents protocol mechanisms, not marketing claims. All specifications are verifiable from the reference implementation. Citations should reference the DOI and GitHub implementation.\n\nEnd of white paper.\nTimestamp: 2026-02-17T00:12:48.263Z\nSHA256: 54b73e2123b6c91e22ad4825f07ad29187e330c57de1457aea50ccf471a05f6e\nBrand: ACBEATZ.COM\nCrypto receipt: ✅ Complete (local SHA-256)\nCourt / audit / dispute: ✅ Strong baseline (pair with artifact)\nIntegrity rule: NON-COPIABLE WHEN HASH-CHAIN BROKEN\n© 2026 ACBEATZ.COM — All rights reserved.",
  "machine_hard": {
    "mh8_system": "ACBEATZ.COM",
    "brand": "ACBEATZ.COM",
    "created_utc": "2026-02-17T00:12:48.263Z",
    "integrity_rule": "NON-COPIABLE WHEN HASH-CHAIN BROKEN",
    "core_payload": {
      "mh8_system": "MH8-PROTOCOL-HUB",
      "receipt_type": "MH8-PROTOCOL-HUB-CORE-MINT",
      "receipt_version": "PROTOCOL_HUB_UI_V13",
      "created_utc": "2026-02-17T00:12:48.263Z",
      "artifact": {
        "core_entry": "[https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\nhttps://github.com/acbeatz\nhttps://acbeatz.com/n-eyes\nhttps://orcid.org/0009-0003-3846-9082]\n\nMH8-Music-Prompt-Pro: A Single-File Protocol for Deterministic Provenance in AI Music Prompting\nTechnical White Paper\nVersion 1.0.0\nPublished: February 16, 2026\nAuthor: ACBEATZ.COM (MH8 Research Lab)\nDOI: [To be assigned upon Zenodo upload]\n\nAbstract\nThis paper documents MH8-Music-Prompt-Pro, a single-file, dependency-free Single Page Application (SPA) that implements a deterministic provenance protocol for AI music generation prompts. The system introduces six novel mechanisms:\n\nTriple-layer proof-of-creation receipts with embedded SHA-256 cryptographic anchors\n\nCanonical JSON serialization for creative artifact hashing\n\nContinuity-lock mechanism for musical parameter provenance (GENRE/BPM/KEY_SIGNATURE)\n\nMulti-portal structured prompting schema optimized for Suno/Udio platforms\n\nBracketed hook-pack ingestion and injection pipeline\n\nUser-embedded SHA256_ID serialization bridging human tracking with machine verification\n\nThe protocol ensures reproducible, timestamped, auditable prompt provenance from human creation through AI generation, addressing a critical gap in AI music workflow transparency.\n\nKeywords: AI music provenance, deterministic hashing, structured prompting, continuity lock, creative artifact serialization, single-file SPA\n\n1. Introduction\nAI music platforms (Suno, Udio) accept unstructured prompts combining lyrics, style tags, and production directives. No existing system provides:\n\nCryptographic proof-of-origin for the prompt itself\n\nParameter continuity tracking (GENRE/BPM/KEY locking)\n\nStructured multi-layer serialization separating lyrics from metadata\n\nHook ecosystem extensibility for reusable prompt components\n\nSingle-file deployment across all environments\n\nMH8-Music-Prompt-Pro solves these gaps through a protocol-first UI that generates triple-layer receipts:\n\ntext\nLayer 1: LYRICS → Platform lyrics field\nLayer 2: STYLE/TAGS/SETTINGS → Platform prompt field  \nLayer 3: HASHED_RECEIPT → Private cryptographic proof\nEach receipt contains a deterministic SHA-256 hash of canonical JSON payload:\n\ntext\nhash = SHA256(\"MH8-Acbeatz.com|\" + stableStringify(core_payload))\n2. Protocol Architecture\n2.1 Multi-Portal Schema\nThe system defines 22 canonical portals representing the complete AI music prompt surface area:\n\ntext\nCORE: MODEL_BEHAVIOR_CONTROL, LYRICS, GENRE, MOOD, ENERGY, VOCAL_INTENSITY\nPARAMETERS: ERA_INFLUENCE, BPM, KEY_SIGNATURE\nINSTRUMENTS: BACKING_VOX, DRUMS, GUITAR, BASS, PIANO_KEYS, SYNTH, PERCUSSION\nPRODUCTION: DYNAMICS, MIX, MASTER\nEXTENSION: EXTRA_HOOKS, NOTES, CONTINUITY_LOCK, SHA256_ID\nThree tiered flows expose portal subsets:\n\nQUICK: 7 portals (behavior, lyrics, core style, tracking)\n\nSEMI: 19 portals (full production stack)\n\nADVANCED: 22 portals (complete surface area)\n\n2.2 Triple-Layer Receipt Model\ntext\nLAYER 1 (Platform-ready):\n[LAYER 1 — LYRICS (PASTE THIS)]\n{raw lyrics text}\n\nLAYER 2 (Platform-ready):  \n[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\n• [PLATFORM] Suno\n• [GENRE] rock\n• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY: A\n• [SHA256_ID] b9ac803080cc52ad0c7f09594240f578421f86cddbc49561954a74d2ed2ed39a\n[SHA 256 ID + {minted_hash_hex}]\n\nLAYER 3 (Cryptographic proof):\nmachine_hard: {\n  core_payload: {portals, continuity_lock, platform, timestamp},\n  canonical_payload: \"{frozen JSON string}\",\n  hash_input: \"MH8-Acbeatz.com|{canonical_payload}\",\n  sha256_hex: \"{deterministic hash}\",\n  cryptographic_receipt_status: \"VALID\"\n}\n2.3 Canonical Hashing Pipeline\ntext\n1. core_payload = {\n     system: \"MH8-Music-Prompt-Pro\",\n     version: \"1.0.0\", \n     brand: \"ACBEATZ.COM\",\n     platform: \"Suno\",\n     prompt_name: \"Prompt 1\",\n     created_utc: \"2026-02-16T16:15:00Z\",\n     portals: {LYRICS: \"...\", GENRE: \"...\", ...},\n     continuity_lock: {engaged: true, genre: \"rock\", bpm: 88, key_signature: \"A\"},\n     imported_hooks_count: 200\n   }\n\n2. canonical = stableStringify(core_payload)  // deterministic JSON\n\n3. hash_input = \"MH8-Acbeatz.com|\" + canonical\n\n4. minted_hash = SHA256(hash_input)  // crypto.subtle.digest('SHA-256')\nKey innovation: The minted SHA-256 hash is serialized back into Layer 2 as the final line, creating a closed verification loop:\n\ntext\nPrompt → AI Platform → Track → Hash matches Layer 3 → Provenance confirmed\n3. Continuity Lock Mechanism\nNovel contribution: Parameterized continuity tracking for musical elements.\n\ntext\n1. DISENGAGED: Normal editing of GENRE/BPM/KEY_SIGNATURE portals\n2. ADD_LOCK: Snapshot current portal values → {engaged: true, genre: X, bpm: Y, key: Z}\n3. ENGAGE: Lock portals read-only, apply snapshot values, visual LOCKED state\n4. Locked portals reject edits, toast \"Continuity lock active\", re-apply values\nReceipt serialization:\n\ntext\n[CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A\nVerification: Any observer can confirm the track's GENRE/BPM/KEY matches the locked snapshot at prompt creation time.\n\n4. Bracketed Hook-Pack Ingestion\nProtocol: JSON files containing bracketed strings ([Performance: Emotion | choose 1 word | commit]) are:\n\nParsed: Bracketed regex extraction → flat array of hooks\n\nMerged: Union of all imported packs → state.hooks[]\n\nFiltered: Pack selector chips toggle between \"MERGED\" vs individual packs\n\nInjected: Click-to-append into any portal via modal editor\n\nScalability: Infinite hook packs, deterministic merging, pack-level filtering preserves provenance.\n\n5. Single-File SPA Implementation\nArchitecture density (97kb minified):\n\ntext\n- 2000+ lines vanilla HTML/CSS/JS\n- CSS custom properties + light/dark theming\n- ARIA-complete accessibility (tablist, dialog, status)\n- Keyboard navigation (←→ Enter, tab cycling)\n- Clipboard API + legacy textarea fallback\n- crypto.subtle SHA256 + Uint8Array polyfill\n- localStorage vault persistence\n- Mobile viewport-fit=cover hardening\nNo dependencies: Ships as one HTML file. Runs in any modern browser.\n\n6. Prompt Entries Completed (Verification Surface)\nCritical UX/protocol bridge: Button exposes Layer 1 + Layer 2 only with:\n\ntext\n[PROMPT ENTRIES COMPLETED — COPY/PASTE]\nINSTRUCTIONS: Copy/paste the two blocks below into your AI music platform.\n\n[LAYER 1 — LYRICS (PASTE THIS)]\n{lyrics}\n\n[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\n• [PLATFORM] CUSTOM\n• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A\n• [SHA256_ID] {user_tracking_id}\n[SHA 256 ID + {minted_sha256_hex}]\nThe final line ([SHA 256 ID + {minted_hash}]) embeds machine-readable verification directly in the human-facing prompt.\n\n7. Verification Workflow\nGiven a Suno/Udio track with embedded SHA-256:\n\ntext\n1. Extract Layer 2 final line → minted_hash = \"b513cff2c553aeb8...\"\n2. Reconstruct core_payload from Layer 1+2+user SHA256_ID\n3. canonical = stableStringify(core_payload)\n4. hash_input = \"MH8-Acbeatz.com|\" + canonical  \n5. computed_hash = SHA256(hash_input)\n6. IF computed_hash == minted_hash → PROVENANCE VERIFIED\n8. Related Work & Contributions\nExisting systems lack:\n\nCryptographic prompt provenance\n\nContinuity locking for musical parameters\n\nStructured multi-layer prompt serialization\n\nHook ecosystem extensibility\n\nDeterministic canonicalization rules\n\nNovel contributions:\n\nFirst deterministic provenance protocol for AI music prompts\n\nContinuity-lock mechanism for musical parameter integrity\n\nTriple-layer receipt model bridging human/AI/machine verification\n\nBracketed hook-pack standard for prompt component reusability\n\nSingle-file SPA reference implementation (97kb, zero dependencies)\n\n9. Implementation Availability\ntext\nGitHub: github.com/acbeatz/mh8-music-prompt-pro\nZenodo: [DOI pending]\nSingle-file SPA: MH8-Music-Prompt-Pro-v1.0.0.html (97kb)\nSchema: PORTAL_KEYS.json (canonical portal definitions)\nReceipt spec: TRIPLE_LAYER_RECEIPT_v1.md\n10. Future Work\nMulti-agent verification: Third-party receipt validation service\n\nBlockchain anchoring: Periodic SHA-256 batching to public ledger\n\nProtocol v2: Vector embeddings of hook packs for semantic search\n\nCross-platform expansion: Stable Audio, MusicGen, Riffusion\n\nFormal verification: Coq/HOL proofs of canonicalization determinism\n\nAcknowledgments\nBuilt atop MH8-R-R fact-checking protocol research. Deployed as ACBEATZ.COM production tool.\n\nCopyright © 2026 ACBEATZ.COM\nLicense: CC-BY-4.0 (attribution required for academic use)\nContact: research@acbeatz.com\n\nThis paper documents protocol mechanisms, not marketing claims. All specifications are verifiable from the reference implementation. Citations should reference the DOI and GitHub implementation.\n\nEnd of white paper."
      }
    },
    "hash_input": "ACBEATZ.COM|{\"artifact\":{\"core_entry\":\"[https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\\nhttps://github.com/acbeatz\\nhttps://acbeatz.com/n-eyes\\nhttps://orcid.org/0009-0003-3846-9082]\\n\\nMH8-Music-Prompt-Pro: A Single-File Protocol for Deterministic Provenance in AI Music Prompting\\nTechnical White Paper\\nVersion 1.0.0\\nPublished: February 16, 2026\\nAuthor: ACBEATZ.COM (MH8 Research Lab)\\nDOI: [To be assigned upon Zenodo upload]\\n\\nAbstract\\nThis paper documents MH8-Music-Prompt-Pro, a single-file, dependency-free Single Page Application (SPA) that implements a deterministic provenance protocol for AI music generation prompts. The system introduces six novel mechanisms:\\n\\nTriple-layer proof-of-creation receipts with embedded SHA-256 cryptographic anchors\\n\\nCanonical JSON serialization for creative artifact hashing\\n\\nContinuity-lock mechanism for musical parameter provenance (GENRE/BPM/KEY_SIGNATURE)\\n\\nMulti-portal structured prompting schema optimized for Suno/Udio platforms\\n\\nBracketed hook-pack ingestion and injection pipeline\\n\\nUser-embedded SHA256_ID serialization bridging human tracking with machine verification\\n\\nThe protocol ensures reproducible, timestamped, auditable prompt provenance from human creation through AI generation, addressing a critical gap in AI music workflow transparency.\\n\\nKeywords: AI music provenance, deterministic hashing, structured prompting, continuity lock, creative artifact serialization, single-file SPA\\n\\n1. Introduction\\nAI music platforms (Suno, Udio) accept unstructured prompts combining lyrics, style tags, and production directives. No existing system provides:\\n\\nCryptographic proof-of-origin for the prompt itself\\n\\nParameter continuity tracking (GENRE/BPM/KEY locking)\\n\\nStructured multi-layer serialization separating lyrics from metadata\\n\\nHook ecosystem extensibility for reusable prompt components\\n\\nSingle-file deployment across all environments\\n\\nMH8-Music-Prompt-Pro solves these gaps through a protocol-first UI that generates triple-layer receipts:\\n\\ntext\\nLayer 1: LYRICS → Platform lyrics field\\nLayer 2: STYLE/TAGS/SETTINGS → Platform prompt field  \\nLayer 3: HASHED_RECEIPT → Private cryptographic proof\\nEach receipt contains a deterministic SHA-256 hash of canonical JSON payload:\\n\\ntext\\nhash = SHA256(\\\"MH8-Acbeatz.com|\\\" + stableStringify(core_payload))\\n2. Protocol Architecture\\n2.1 Multi-Portal Schema\\nThe system defines 22 canonical portals representing the complete AI music prompt surface area:\\n\\ntext\\nCORE: MODEL_BEHAVIOR_CONTROL, LYRICS, GENRE, MOOD, ENERGY, VOCAL_INTENSITY\\nPARAMETERS: ERA_INFLUENCE, BPM, KEY_SIGNATURE\\nINSTRUMENTS: BACKING_VOX, DRUMS, GUITAR, BASS, PIANO_KEYS, SYNTH, PERCUSSION\\nPRODUCTION: DYNAMICS, MIX, MASTER\\nEXTENSION: EXTRA_HOOKS, NOTES, CONTINUITY_LOCK, SHA256_ID\\nThree tiered flows expose portal subsets:\\n\\nQUICK: 7 portals (behavior, lyrics, core style, tracking)\\n\\nSEMI: 19 portals (full production stack)\\n\\nADVANCED: 22 portals (complete surface area)\\n\\n2.2 Triple-Layer Receipt Model\\ntext\\nLAYER 1 (Platform-ready):\\n[LAYER 1 — LYRICS (PASTE THIS)]\\n{raw lyrics text}\\n\\nLAYER 2 (Platform-ready):  \\n[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\\n• [PLATFORM] Suno\\n• [GENRE] rock\\n• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY: A\\n• [SHA256_ID] b9ac803080cc52ad0c7f09594240f578421f86cddbc49561954a74d2ed2ed39a\\n[SHA 256 ID + {minted_hash_hex}]\\n\\nLAYER 3 (Cryptographic proof):\\nmachine_hard: {\\n  core_payload: {portals, continuity_lock, platform, timestamp},\\n  canonical_payload: \\\"{frozen JSON string}\\\",\\n  hash_input: \\\"MH8-Acbeatz.com|{canonical_payload}\\\",\\n  sha256_hex: \\\"{deterministic hash}\\\",\\n  cryptographic_receipt_status: \\\"VALID\\\"\\n}\\n2.3 Canonical Hashing Pipeline\\ntext\\n1. core_payload = {\\n     system: \\\"MH8-Music-Prompt-Pro\\\",\\n     version: \\\"1.0.0\\\", \\n     brand: \\\"ACBEATZ.COM\\\",\\n     platform: \\\"Suno\\\",\\n     prompt_name: \\\"Prompt 1\\\",\\n     created_utc: \\\"2026-02-16T16:15:00Z\\\",\\n     portals: {LYRICS: \\\"...\\\", GENRE: \\\"...\\\", ...},\\n     continuity_lock: {engaged: true, genre: \\\"rock\\\", bpm: 88, key_signature: \\\"A\\\"},\\n     imported_hooks_count: 200\\n   }\\n\\n2. canonical = stableStringify(core_payload)  // deterministic JSON\\n\\n3. hash_input = \\\"MH8-Acbeatz.com|\\\" + canonical\\n\\n4. minted_hash = SHA256(hash_input)  // crypto.subtle.digest('SHA-256')\\nKey innovation: The minted SHA-256 hash is serialized back into Layer 2 as the final line, creating a closed verification loop:\\n\\ntext\\nPrompt → AI Platform → Track → Hash matches Layer 3 → Provenance confirmed\\n3. Continuity Lock Mechanism\\nNovel contribution: Parameterized continuity tracking for musical elements.\\n\\ntext\\n1. DISENGAGED: Normal editing of GENRE/BPM/KEY_SIGNATURE portals\\n2. ADD_LOCK: Snapshot current portal values → {engaged: true, genre: X, bpm: Y, key: Z}\\n3. ENGAGE: Lock portals read-only, apply snapshot values, visual LOCKED state\\n4. Locked portals reject edits, toast \\\"Continuity lock active\\\", re-apply values\\nReceipt serialization:\\n\\ntext\\n[CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: 2026-02-16T02:52:52Z | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A\\nVerification: Any observer can confirm the track's GENRE/BPM/KEY matches the locked snapshot at prompt creation time.\\n\\n4. Bracketed Hook-Pack Ingestion\\nProtocol: JSON files containing bracketed strings ([Performance: Emotion | choose 1 word | commit]) are:\\n\\nParsed: Bracketed regex extraction → flat array of hooks\\n\\nMerged: Union of all imported packs → state.hooks[]\\n\\nFiltered: Pack selector chips toggle between \\\"MERGED\\\" vs individual packs\\n\\nInjected: Click-to-append into any portal via modal editor\\n\\nScalability: Infinite hook packs, deterministic merging, pack-level filtering preserves provenance.\\n\\n5. Single-File SPA Implementation\\nArchitecture density (97kb minified):\\n\\ntext\\n- 2000+ lines vanilla HTML/CSS/JS\\n- CSS custom properties + light/dark theming\\n- ARIA-complete accessibility (tablist, dialog, status)\\n- Keyboard navigation (←→ Enter, tab cycling)\\n- Clipboard API + legacy textarea fallback\\n- crypto.subtle SHA256 + Uint8Array polyfill\\n- localStorage vault persistence\\n- Mobile viewport-fit=cover hardening\\nNo dependencies: Ships as one HTML file. Runs in any modern browser.\\n\\n6. Prompt Entries Completed (Verification Surface)\\nCritical UX/protocol bridge: Button exposes Layer 1 + Layer 2 only with:\\n\\ntext\\n[PROMPT ENTRIES COMPLETED — COPY/PASTE]\\nINSTRUCTIONS: Copy/paste the two blocks below into your AI music platform.\\n\\n[LAYER 1 — LYRICS (PASTE THIS)]\\n{lyrics}\\n\\n[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\\n• [PLATFORM] CUSTOM\\n• [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: rock | BPM: 88 | KEY_SIGNATURE: A\\n• [SHA256_ID] {user_tracking_id}\\n[SHA 256 ID + {minted_sha256_hex}]\\nThe final line ([SHA 256 ID + {minted_hash}]) embeds machine-readable verification directly in the human-facing prompt.\\n\\n7. Verification Workflow\\nGiven a Suno/Udio track with embedded SHA-256:\\n\\ntext\\n1. Extract Layer 2 final line → minted_hash = \\\"b513cff2c553aeb8...\\\"\\n2. Reconstruct core_payload from Layer 1+2+user SHA256_ID\\n3. canonical = stableStringify(core_payload)\\n4. hash_input = \\\"MH8-Acbeatz.com|\\\" + canonical  \\n5. computed_hash = SHA256(hash_input)\\n6. IF computed_hash == minted_hash → PROVENANCE VERIFIED\\n8. Related Work & Contributions\\nExisting systems lack:\\n\\nCryptographic prompt provenance\\n\\nContinuity locking for musical parameters\\n\\nStructured multi-layer prompt serialization\\n\\nHook ecosystem extensibility\\n\\nDeterministic canonicalization rules\\n\\nNovel contributions:\\n\\nFirst deterministic provenance protocol for AI music prompts\\n\\nContinuity-lock mechanism for musical parameter integrity\\n\\nTriple-layer receipt model bridging human/AI/machine verification\\n\\nBracketed hook-pack standard for prompt component reusability\\n\\nSingle-file SPA reference implementation (97kb, zero dependencies)\\n\\n9. Implementation Availability\\ntext\\nGitHub: github.com/acbeatz/mh8-music-prompt-pro\\nZenodo: [DOI pending]\\nSingle-file SPA: MH8-Music-Prompt-Pro-v1.0.0.html (97kb)\\nSchema: PORTAL_KEYS.json (canonical portal definitions)\\nReceipt spec: TRIPLE_LAYER_RECEIPT_v1.md\\n10. Future Work\\nMulti-agent verification: Third-party receipt validation service\\n\\nBlockchain anchoring: Periodic SHA-256 batching to public ledger\\n\\nProtocol v2: Vector embeddings of hook packs for semantic search\\n\\nCross-platform expansion: Stable Audio, MusicGen, Riffusion\\n\\nFormal verification: Coq/HOL proofs of canonicalization determinism\\n\\nAcknowledgments\\nBuilt atop MH8-R-R fact-checking protocol research. Deployed as ACBEATZ.COM production tool.\\n\\nCopyright © 2026 ACBEATZ.COM\\nLicense: CC-BY-4.0 (attribution required for academic use)\\nContact: research@acbeatz.com\\n\\nThis paper documents protocol mechanisms, not marketing claims. All specifications are verifiable from the reference implementation. Citations should reference the DOI and GitHub implementation.\\n\\nEnd of white paper.\"},\"created_utc\":\"2026-02-17T00:12:48.263Z\",\"mh8_system\":\"MH8-PROTOCOL-HUB\",\"receipt_type\":\"MH8-PROTOCOL-HUB-CORE-MINT\",\"receipt_version\":\"PROTOCOL_HUB_UI_V13\"}",
    "hash_input_stats": {
      "hash_input_bytes": 9304,
      "LF": 0,
      "CRLF": 0,
      "CR": 0,
      "endsWithNewline": "NO",
      "preview_first_80": "ACBEATZ.COM|{\"artifact\":{\"core_entry\":\"[https://zenodo.org/uploads/18665540][htt",
      "preview_last_80": "eipt_type\":\"MH8-PROTOCOL-HUB-CORE-MINT\",\"receipt_version\":\"PROTOCOL_HUB_UI_V13\"}"
    },
    "sha256_hex": "54b73e2123b6c91e22ad4825f07ad29187e330c57de1457aea50ccf471a05f6e",
    "cryptographic_receipt_status": "VERIFIED_LOCAL",
    "court_audit_dispute_evidence_status": "COMPLETE_FOR_BASIC_DISPUTE",
    "reproducible_hashing_instructions": [
      "1) Rebuild core_payload exactly (same keys/values).",
      "2) Canonicalize core_payload as stableStringify (sorted keys, no whitespace).",
      "3) Build hash_input = brand + '|' + canonical_core_payload.",
      "4) SHA-256 over UTF-8 bytes of hash_input => sha256_hex.",
      "5) Compare computed sha256_hex to this receipt's sha256_hex."
    ],
    "sealing_metadata": {
      "sealed_by": "MH8 Protocol Hub (client-side)",
      "sealing_mode": "SELF_SEALED_LOCAL_SHA256",
      "note": "Keep exported receipt + context artifact for provenance."
    }
  }
}
VERIFIED
VERIFIED — hash matches (receipt.hash_input (dual-layer)).
computed sha256
54b73e2123b6c91e22ad4825f07ad29187e330c57de1457aea50ccf471a05f6e
expected sha256
54b73e2123b6c91e22ad4825f07ad29187e330c57de1457aea50ccf471a05f6e
matches?
YES
hash input bytes
9304 bytes
newline stats
CRLF=0 | LF=0 | CR=0 | endsWithNewline=NO
hash input preview
ACBEATZ.COM|{"artifact":{"core_entry":"[https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\nhttps://github.com/acbeatz\n … eipt_type":"MH8-PROTOCOL-HUB-CORE-MINT","receipt_version":"PROTOCOL_HUB_UI_V13"}







||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[code sealed sha256 Mh8 Music Prompt Pro2-16-2026]




PASS ✅
Brand: ACBEATZ.COM
Claimed sha256_hex: 3c3e31eb12b36f7ebc11da31a314f6725cb344ad92267a757dc870088f22aa06
Computed sha256_hex: 3c3e31eb12b36f7ebc11da31a314f6725cb344ad92267a757dc870088f22aa06
hash_input_bytes: 123021 | LF=0 CRLF=0 CR=0 | endsWithNewline=NO
hash_input first: ACBEATZ.COM|{"artifact":{"core_entry":"[MH8-Music Prompt Pro User Interface sha2
hash_input last: eipt_type":"MH8-PROTOCOL-HUB-CORE-MINT","receipt_version":"PROTOCOL_HUB_UI_V13"}

{
  "human_pretty": "ACBEATZ.COM — MH8 Protocol Hub\nReceipt: Core Entry Mint (dual-layer)\nCore Entry: [MH8-Music Prompt Pro User Interface sha256 Sealed Code]\n[https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\nhttps://github.com/acbeatz\nhttps://acbeatz.com/n-eyes\nhttps://orcid.org/0009-0003-3846-9082]\n\n<!DOCTYPE html>  \n<html lang=\"en\">\n<head>\n  <!--\n    MH8 AI Music Proof-of-Creation Vault (Vanilla HTML5 SPA)\n    Version: 2.0.0\n    © 2026 ACBEATZ.COM\n    Commercial SaaS Ready | Legacy + Mobile Hardened\n\n    Shell: PROMPT UI FORMAT (canonical)\n    Converted from: MH8 AI Music Proof-of-Creation UI (style + lyrics + hooks + triple-layer receipt)\n\n    Core behavior preserved from PROMPT UI FORMAT:\n    - Single-file SPA, no dependencies\n    - Topbar: Platform select, Import Hooks JSON + Imports Manager, theme toggle, log toggle\n    - Prompt tabs w/ per-tab delete\n    - Entry Portals: tiered carousel (Quick/Semi/Advanced) with Prev/Next/dots/jump + keyboard arrows + modal editor\n    - Hooks import: bracketed string extraction; select/append; manual typing supported\n    - Imports Manager: list/merge/rebuild/delete/clear/export string-array of all imports\n    - Three-layer receipt minting + collapsible receipt header toggle + ARIA expanded\n    - PROMPT ENTRIES COMPLETED module: shows Layer 1+2 only with Copy/Clear/Export\n    - Local vault log: per-entry copy + export/clear\n    - Clipboard API + legacy fallback, toast + ARIA live, SHA-256 via crypto.subtle + fallback\n    - Inline SVG \"8\" favicon\n\n    Conversion changes for AI Music:\n    - Portal schema renamed for music prompts\n    - Receipt layers redefined:\n      Layer 1 = LYRICS (PASTE)\n      Layer 2 = STYLE / TAGS / SETTINGS (PASTE)\n      Layer 3 = HASHED RECEIPT (DO NOT PASTE)\n    - All headers/titles/labels updated to MH8 AI Music naming\n\n    UPDATE (Additive only):\n    - Portal Editor module sub-header menu listing ALL imported JSON hook packs (clickable)\n    - Allows selecting a specific pack OR MERGED (all) directly inside Portal Editor\n    - Hook list in Portal Editor filters to the selected pack; Append works exactly the same\n\n    UPDATE #1 (Additive only):\n    - Retitle UI header/title to: \"MH8-Music-Prompt-Pro\" (Ai Music-Creators-Console)\n    - All prior logic/features/functions remain verbatim.\n\n    UPDATE #2 (Additive only) — CONTINUITY LOCK FIX:\n    - CONTINUITY LOCK portal now supports: DISENGAGED / ENGAGED + ADD (snapshot) behavior\n    - When ENGAGED: locks GENRE + BPM + KEY_SIGNATURE (read-only in modal + updates blocked)\n    - Continuity lock content is included in Layer 2 + full receipt payload (portals + layers)\n    - Lock snapshot is deterministic and stored per-tab (does not affect other prompts)\n\n    UPDATE #3 (Additive only) — SHA256 ID PORTAL:\n    - Add a final portal to the END of each portals menu section (Quick/Semi/Advanced) titled \"SHA256 ID\"\n    - This portal holds the user’s SHA256 tracking # (production tracking ID)\n    - SHA256 ID portal entry is included in Layer 2 + full receipt payload\n    - Minted SHA256 (Layer 3 hash) remains present after minting as proof\n    - END OF UPDATES\n\n    UPDATE #4 (Additive only) — CONTINUITY LOCK RECOGNIZES USER PORTAL INPUTS:\n    - When ENGAGE is pressed (or when continuity is engaged programmatically), the lock now captures the CURRENT\n      portal values for GENRE, BPM, KEY_SIGNATURE and stores them into the continuity lock snapshot before enforcing.\n    - This ensures receipts show non-blank values like:\n      [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: X | BPM: Y | KEY_SIGNATURE: Z\n    - All other behavior remains unchanged.\n\n    UPDATE #5 (Additive only) — PROMPT ENTRIES COMPLETED MUST INCLUDE MINTED SHA256:\n    - When user clicks \"Mint Proof Receipt\", the minted SHA256 hash for that receipt is included in the\n      PROMPT ENTRIES COMPLETED output as the LAST entry:\n      [SHA 256 ID + hex64]\n    - Every minted receipt updates PROMPT ENTRIES COMPLETED with its unique SHA256 appended as final line.\n  -->\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\" />\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <title>MH8-Music-Prompt-Pro (Ai Music-Creators-Console)</title>\n\n  <!-- SVG \"8\" favicon -->\n  <link rel=\"icon\" type=\"image/svg+xml\"\n    href='data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><rect width=\"64\" height=\"64\" rx=\"14\" fill=\"%2306070b\"/><path d=\"M32 12c-7 0-12 4.6-12 11 0 4.1 2.2 7.4 5.7 9.2C21.4 34 18 37.8 18 43c0 6.8 6 11 14 11s14-4.2 14-11c0-5.2-3.4-9-7.7-10.8C41.8 30.4 44 27.1 44 23c0-6.4-5-11-12-11zm0 6c3.7 0 6 2 6 5 0 3.1-2.5 5-6 5s-6-1.9-6-5c0-3 2.3-5 6-5zm0 16c4.7 0 8 2.7 8 6.5S36.7 47 32 47s-8-2.7-8-6.5S27.3 34 32 34z\" fill=\"%234fd1c5\"/></svg>' />\n\n  <style>\n    :root{\n      --bg:#06070b;--panel:#0d1224;--panel2:#0b1020;--text:#e9ecf5;--muted:#a7b0c3;\n      --line:rgba(255,255,255,.10);--accent:#4fd1c5;--accent2:#7c3aed;--danger:#f56565;\n      --shadow:0 12px 40px rgba(0,0,0,.35);\n      --radius:14px;\n      --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n      --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\",\"Segoe UI Emoji\";\n      --orange:#f59e0b;\n    }\n    html[data-theme=\"light\"]{\n      --bg:#f6f7fb;--panel:#ffffff;--panel2:#ffffff;--text:#0b1220;--muted:#4b5565;\n      --line:rgba(15,23,42,.12);--shadow:0 16px 50px rgba(2,6,23,.10);\n    }\n    *{box-sizing:border-box}\n    body{margin:0;font-family:var(--sans);background:var(--bg);color:var(--text);min-height:100vh;}\n    .wrap{max-width:1200px;margin:0 auto;padding:14px}\n    .topbar{\n      display:flex;align-items:center;justify-content:space-between;gap:12px;\n      padding:12px 12px;border:1px solid var(--line);border-radius:var(--radius);\n      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));\n      box-shadow:var(--shadow);\n    }\n    html[data-theme=\"light\"] .topbar{background:linear-gradient(180deg, rgba(2,6,23,.03), rgba(2,6,23,.01))}\n    .brand{display:flex;flex-direction:column;gap:2px}\n    .brand h1{margin:0;font-size:16px;letter-spacing:.2px}\n    .brand .sub{font-size:12px;color:var(--muted)}\n    .controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end}\n    .select, .btn{\n      font-size:13px;border-radius:12px;border:1px solid var(--line);\n      background:rgba(255,255,255,.03);color:var(--text);\n      padding:9px 10px;outline:none;\n    }\n    html[data-theme=\"light\"] .select, html[data-theme=\"light\"] .btn{background:rgba(2,6,23,.03)}\n    .btn{cursor:pointer;user-select:none}\n    .btn:focus{box-shadow:0 0 0 3px rgba(79,209,197,.22)}\n    .btn.primary{border-color:rgba(79,209,197,.45); background:rgba(79,209,197,.10)}\n    .btn.secondary{border-color:rgba(124,58,237,.35); background:rgba(124,58,237,.10)}\n    .btn.danger{border-color:rgba(245,101,101,.35); background:rgba(245,101,101,.10)}\n    .btn.small{padding:7px 9px;border-radius:10px;font-size:12px}\n    .btn.icon{padding:7px 9px;border-radius:10px;font-size:12px;display:inline-flex;gap:6px;align-items:center}\n    .btn[disabled]{opacity:.55;cursor:not-allowed}\n\n    .tabsBar{margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;}\n    .tablist{\n      display:flex;gap:8px;align-items:center;flex-wrap:wrap;\n      padding:10px;border:1px solid var(--line);border-radius:var(--radius);\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .tablist{background:rgba(2,6,23,.02)}\n    .tabWrap{display:flex;align-items:center;gap:6px}\n    .tab{\n      border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text);\n      padding:8px 10px;border-radius:12px;cursor:pointer;font-size:13px;\n    }\n    html[data-theme=\"light\"] .tab{background:rgba(2,6,23,.03)}\n    .tab[aria-selected=\"true\"]{border-color:rgba(79,209,197,.55);background:rgba(79,209,197,.12);}\n    .tab:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.22)}\n    .tabDel{\n      border:1px solid var(--line);background:rgba(255,255,255,.02);color:var(--muted);\n      padding:8px 9px;border-radius:12px;cursor:pointer;font-size:12px;\n    }\n    html[data-theme=\"light\"] .tabDel{background:rgba(2,6,23,.02)}\n    .tabDel:hover{color:var(--text);border-color:rgba(245,101,101,.35);background:rgba(245,101,101,.10)}\n\n    .grid{margin-top:12px;display:grid;grid-template-columns:1fr;gap:12px;}\n    .footerZone{margin-top:12px;}\n    .footerZone .card{width:100%;}\n\n    .card{\n      border:1px solid var(--line);border-radius:var(--radius);\n      background:rgba(255,255,255,.02);box-shadow:var(--shadow);overflow:hidden;\n    }\n    html[data-theme=\"light\"] .card{background:rgba(2,6,23,.02)}\n    .cardHeader{\n      display:flex;align-items:center;justify-content:space-between;gap:10px;\n      padding:12px 12px;border-bottom:1px solid var(--line);\n    }\n    .cardHeader h2{margin:0;font-size:13px;letter-spacing:.2px}\n    .cardHeader .hint{color:var(--muted);font-size:12px}\n    .cardBody{padding:12px}\n    .actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}\n    .headerOrange{color:var(--orange);}\n\n    .copyrightFooter{\n      margin-top:10px;\n      padding:10px 12px;\n      border:1px solid var(--line);\n      border-radius:var(--radius);\n      background:rgba(255,255,255,.02);\n      color:var(--muted);\n      font-size:12px;\n      text-align:center;\n    }\n    html[data-theme=\"light\"] .copyrightFooter{background:rgba(2,6,23,.02)}\n\n    .portal{\n      border:1px solid var(--line);\n      border-radius:14px;\n      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));\n      padding:10px;\n      cursor:pointer;\n      transition:transform .12s ease, border-color .12s ease, background .12s ease;\n      min-height:120px;\n      position:relative;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .portal{background:linear-gradient(180deg, rgba(2,6,23,.03), rgba(2,6,23,.01))}\n    .portal:hover{transform:translateY(-1px)}\n    .portal.active{\n      border-color:rgba(79,209,197,.55);\n      box-shadow:0 0 0 3px rgba(79,209,197,.12) inset;\n    }\n    .portal.locked{\n      border-color:rgba(245,158,11,.55);\n      box-shadow:0 0 0 3px rgba(245,158,11,.12) inset;\n    }\n    .pHead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}\n    .pTitle{font-size:12px;letter-spacing:.25px;color:var(--orange);}\n    .pBadge{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:3px 8px;\n      background:rgba(255,255,255,.03);\n      white-space:nowrap;\n    }\n    html[data-theme=\"light\"] .pBadge{background:rgba(2,6,23,.03)}\n    .pValue{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.02);\n      font-family:var(--mono);\n      font-size:12px;\n      color:var(--text);\n      white-space:pre-wrap;\n      word-break:break-word;\n      min-height:60px;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .pValue{background:rgba(2,6,23,.02)}\n    .pEmpty{color:var(--muted)}\n    .portalNote{margin-top:10px;color:var(--muted);font-size:12px}\n\n    .carouselWrap{\n      border:1px solid var(--line);\n      border-radius:16px;\n      background:rgba(255,255,255,.02);\n      padding:10px;\n      box-shadow:0 10px 28px rgba(0,0,0,.18);\n    }\n    html[data-theme=\"light\"] .carouselWrap{background:rgba(2,6,23,.02)}\n    .carouselTop{\n      display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;\n      margin-bottom:10px;\n    }\n    .carouselLeft{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n    .carouselRight{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}\n    .carouselCounter{\n      font-size:12px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n    }\n    html[data-theme=\"light\"] .carouselCounter{background:rgba(2,6,23,.03)}\n    .carouselDots{\n      display:flex;gap:6px;flex-wrap:wrap;align-items:center;\n      max-width:100%;\n    }\n    .dot{\n      width:9px;height:9px;border-radius:999px;\n      border:1px solid var(--line);\n      background:rgba(255,255,255,.02);\n      cursor:pointer;\n    }\n    html[data-theme=\"light\"] .dot{background:rgba(2,6,23,.02)}\n    .dot.active{\n      border-color:rgba(79,209,197,.65);\n      background:rgba(79,209,197,.25);\n      box-shadow:0 0 0 2px rgba(79,209,197,.10);\n    }\n    .carouselStage{\n      display:grid;\n      grid-template-columns:1fr;\n      gap:10px;\n      min-width:0;\n    }\n    .carouselHintRow{\n      margin-top:8px;\n      display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;\n      color:var(--muted);font-size:12px;\n    }\n    .jumpSelect{\n      font-size:12px;\n      border-radius:12px;\n      border:1px solid var(--line);\n      background:rgba(255,255,255,.03);\n      color:var(--text);\n      padding:8px 10px;\n      outline:none;\n      max-width:100%;\n    }\n    html[data-theme=\"light\"] .jumpSelect{background:rgba(2,6,23,.03)}\n    .kbdHint{\n      font-family:var(--mono);\n      font-size:11px;\n      border:1px dashed var(--line);\n      border-radius:10px;\n      padding:4px 8px;\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .kbdHint{background:rgba(2,6,23,.02)}\n\n    .modeBar{\n      display:flex;gap:6px;flex-wrap:wrap;align-items:center;\n      border:1px solid var(--line);\n      border-radius:999px;\n      padding:4px;\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .modeBar{background:rgba(2,6,23,.02)}\n    .modeBtn{\n      border:1px solid transparent;\n      background:transparent;\n      color:var(--muted);\n      padding:6px 10px;\n      border-radius:999px;\n      cursor:pointer;\n      font-size:12px;\n      line-height:1;\n      user-select:none;\n      white-space:nowrap;\n    }\n    .modeBtn:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.18)}\n    .modeBtn.active{\n      color:var(--text);\n      border-color:rgba(79,209,197,.45);\n      background:rgba(79,209,197,.10);\n    }\n    .modeTag{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n    }\n    html[data-theme=\"light\"] .modeTag{background:rgba(2,6,23,.03)}\n\n    .modalBg{\n      position:fixed;inset:0;background:rgba(0,0,0,.6);\n      display:none;align-items:center;justify-content:center;\n      z-index:9998;padding:14px;\n    }\n    .modal{\n      width:100%;\n      max-width:980px;\n      border:1px solid var(--line);\n      border-radius:16px;\n      background:var(--panel);\n      box-shadow:0 30px 90px rgba(0,0,0,.5);\n      overflow:hidden;\n    }\n    html[data-theme=\"light\"] .modal{background:#fff}\n    .modalTop{\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\n      padding:12px;border-bottom:1px solid var(--line);\n    }\n    .modalTop .t{font-size:13px}\n    .modalTop .s{font-size:12px;color:var(--muted)}\n\n    /* UPDATE: Portal Editor sub-header pack menu (additive) */\n    .packBar{\n      margin-top:8px;\n      display:flex;\n      gap:8px;\n      align-items:center;\n      flex-wrap:wrap;\n    }\n    .packLabel{\n      font-size:12px;\n      color:var(--muted);\n      white-space:nowrap;\n    }\n    .packChips{\n      display:flex;\n      gap:6px;\n      align-items:center;\n      overflow:auto;\n      max-width:100%;\n      padding:4px;\n      border:1px solid var(--line);\n      border-radius:999px;\n      background:rgba(255,255,255,.02);\n      -webkit-overflow-scrolling:touch;\n    }\n    html[data-theme=\"light\"] .packChips{background:rgba(2,6,23,.02)}\n    .packChip{\n      border:1px solid transparent;\n      background:transparent;\n      color:var(--muted);\n      padding:6px 10px;\n      border-radius:999px;\n      cursor:pointer;\n      font-size:12px;\n      line-height:1;\n      user-select:none;\n      white-space:nowrap;\n    }\n    .packChip:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.18)}\n    .packChip.active{\n      color:var(--text);\n      border-color:rgba(79,209,197,.45);\n      background:rgba(79,209,197,.10);\n    }\n    .packActivePill{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);\n      border-radius:999px;\n      padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n      white-space:nowrap;\n    }\n    html[data-theme=\"light\"] .packActivePill{background:rgba(2,6,23,.03)}\n\n    /* UPDATE #2: Continuity lock bar in Portal Editor (additive) */\n    .lockBar{\n      margin-top:8px;\n      display:none;\n      gap:8px;\n      align-items:center;\n      flex-wrap:wrap;\n    }\n    .lockPill{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n      white-space:nowrap;\n    }\n    html[data-theme=\"light\"] .lockPill{background:rgba(2,6,23,.03)}\n    .lockPill.engaged{\n      border-color:rgba(245,158,11,.55);\n      background:rgba(245,158,11,.10);\n      color:var(--text);\n    }\n\n    .modalBody{padding:12px;display:grid;grid-template-columns:1fr;gap:12px}\n    @media(min-width:980px){\n      .modalBody{grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr)}\n    }\n    .fieldLabel{font-size:12px;color:var(--muted);margin:0 0 6px 2px}\n    .modal textarea{\n      width:100%;min-height:220px;resize:vertical;\n      border:1px solid var(--line);border-radius:12px;padding:10px;\n      background:rgba(255,255,255,.03);color:var(--text);\n      font-family:var(--mono);font-size:12px;line-height:1.35;outline:none;\n    }\n    html[data-theme=\"light\"] .modal textarea{background:rgba(2,6,23,.03)}\n    .modal textarea[readonly]{\n      opacity:.9;\n      border-color:rgba(245,158,11,.45);\n      background:rgba(245,158,11,.05);\n      cursor:not-allowed;\n    }\n\n    .hookList{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.02);\n      max-height:260px;\n      overflow:auto;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .hookList{background:rgba(2,6,23,.02)}\n    .hookItem{\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\n      padding:8px;border:1px solid var(--line);border-radius:12px;\n      background:rgba(255,255,255,.02);\n      margin-bottom:8px;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .hookItem{background:rgba(2,6,23,.02)}\n    .hookItem:last-child{margin-bottom:0}\n    .hookText{font-family:var(--mono);font-size:12px;white-space:pre-wrap;word-break:break-word;min-width:0}\n    .hookBtns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}\n\n    pre{\n      margin:0;\n      white-space:pre-wrap;\n      word-break:break-word;\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.03);\n      color:var(--text);\n      font-family:var(--mono);\n      font-size:12px;\n      line-height:1.35;\n      max-height:320px;\n      overflow:auto;\n      min-width:0;\n    }\n    .hidden{display:none !important}\n\n    .logCompact .cardBody{padding:12px}\n    .logViewport{\n      border:1px solid var(--line);\n      border-radius:12px;\n      background:rgba(255,255,255,.02);\n      padding:10px;\n      max-height:220px;\n      overflow:auto;\n    }\n    html[data-theme=\"light\"] .logViewport{background:rgba(2,6,23,.02)}\n    .logEntry{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      margin-bottom:10px;\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .logEntry{background:rgba(2,6,23,.02)}\n    .logEntry:last-child{margin-bottom:0}\n    .logMeta{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between;margin-bottom:8px}\n    .logMeta .left{display:flex;gap:10px;flex-wrap:wrap;align-items:center}\n    .pill{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n    }\n    html[data-theme=\"light\"] .pill{background:rgba(2,6,23,.03)}\n    .pill.locked{\n      border-color:rgba(245,158,11,.55);\n      background:rgba(245,158,11,.10);\n      color:var(--text);\n    }\n    .logActions{display:flex;gap:8px;flex-wrap:wrap}\n    .logPre{\n      white-space:pre-wrap;word-break:break-word;\n      font-family:var(--mono);font-size:12px;line-height:1.35;\n      margin:0;\n    }\n\n    .toastWrap{\n      position:fixed;left:12px; right:12px; bottom:12px;\n      display:flex; justify-content:center;\n      pointer-events:none;z-index:9999;\n    }\n    .toast{\n      pointer-events:none;\n      max-width:920px;width:100%;\n      border:1px solid var(--line);\n      border-radius:14px;\n      padding:10px 12px;\n      background:rgba(13,18,36,.92);\n      color:var(--text);\n      box-shadow:0 18px 60px rgba(0,0,0,.35);\n      display:flex; align-items:flex-start; justify-content:space-between; gap:12px;\n      transform:translateY(14px);\n      opacity:0;\n      transition:opacity .18s ease, transform .18s ease;\n    }\n    html[data-theme=\"light\"] .toast{background:rgba(255,255,255,.95)}\n    .toast.show{opacity:1; transform:translateY(0)}\n    .toast .msg{font-size:13px; line-height:1.3}\n    .toast .tag{\n      font-size:11px;color:var(--muted);white-space:nowrap;\n      border:1px solid var(--line);border-radius:999px;\n      padding:4px 8px;background:rgba(255,255,255,.03);\n    }\n    html[data-theme=\"light\"] .toast .tag{background:rgba(2,6,23,.03)}\n\n    .srOnly{\n      position:absolute !important;\n      width:1px;height:1px;\n      padding:0;margin:-1px;\n      overflow:hidden;clip:rect(0,0,0,0);\n      white-space:nowrap;border:0;\n    }\n    #legacyCopyArea{position:absolute;left:-9999px;top:-9999px}\n\n    .importsSummary{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-top:10px;}\n    .importsList{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.02);\n      max-height:320px;\n      overflow:auto;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .importsList{background:rgba(2,6,23,.02)}\n    .importRow{\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\n      padding:10px;border:1px solid var(--line);border-radius:12px;\n      background:rgba(255,255,255,.02);\n      margin-bottom:10px;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .importRow{background:rgba(2,6,23,.02)}\n    .importRow:last-child{margin-bottom:0}\n    .importMeta{min-width:0}\n    .importName{font-size:12px;color:var(--text);font-family:var(--mono);word-break:break-word}\n    .importSub{font-size:12px;color:var(--muted);margin-top:4px}\n    .importBtns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}\n    .miniNote{font-size:12px;color:var(--muted);line-height:1.35}\n\n    .receiptToggleBtn{\n      appearance:none;border:0;background:transparent;padding:0;margin:0;\n      color:var(--text);font-size:13px;letter-spacing:.2px;cursor:pointer;\n      display:inline-flex;align-items:center;gap:8px;\n    }\n    .receiptToggleBtn:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.22);border-radius:10px}\n    .chev{display:inline-block;width:16px;text-align:center;color:var(--muted);font-family:var(--mono);}\n    .receiptHeaderRight{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;}\n  </style>\n</head>\n\n<body>\n  <div class=\"wrap\">\n    <header class=\"topbar\" role=\"banner\">\n      <div class=\"brand\">\n        <h1>MH8-Music-Prompt-Pro (Ai Music-Creators-Console)</h1>\n        <div class=\"sub\">Vanilla SPA • Music portals + hooks import + triple-layer proof receipts</div>\n      </div>\n\n      <div class=\"controls\" aria-label=\"Top controls\">\n        <label class=\"srOnly\" for=\"platformSelect\">Platform</label>\n        <select id=\"platformSelect\" class=\"select\" aria-label=\"Select music platform\">\n          <option value=\"suno\">Suno</option>\n          <option value=\"udio\">Udio</option>\n          <option value=\"other\">Other / Custom</option>\n        </select>\n\n        <button id=\"importBtn\" class=\"btn secondary\" type=\"button\" aria-haspopup=\"dialog\">Import Hooks JSON</button>\n        <button id=\"importMgrBtn\" class=\"btn secondary icon small\" type=\"button\" aria-haspopup=\"dialog\" title=\"Manage imported JSON files\">☰ Imports</button>\n        <input id=\"importFile\" type=\"file\" accept=\".json,application/json,text/plain\" class=\"srOnly\" />\n\n        <button id=\"themeBtn\" class=\"btn secondary\" type=\"button\" aria-pressed=\"false\">Light Mode</button>\n        <button id=\"logBtn\" class=\"btn\" type=\"button\" aria-expanded=\"false\" aria-controls=\"logCard\">Log</button>\n      </div>\n    </header>\n\n    <div class=\"tabsBar\">\n      <div id=\"tablist\" class=\"tablist\" role=\"tablist\" aria-label=\"Prompt tabs\"></div>\n      <button id=\"addTabBtn\" class=\"btn small primary\" type=\"button\" aria-label=\"Add new prompt tab\">+ Add Prompt</button>\n    </div>\n\n    <main class=\"grid\" role=\"main\">\n      <section class=\"card\" aria-label=\"Prompt editor\">\n        <div class=\"cardHeader\">\n          <h2 class=\"headerOrange\">Music Prompt Editor — Entry Portals</h2>\n          <div class=\"hint\">Step portal-by-portal → stack hooks → Mint proof</div>\n        </div>\n\n        <div class=\"cardBody\">\n          <div class=\"carouselWrap\" aria-label=\"Entry portals carousel\">\n            <div class=\"carouselTop\">\n              <div class=\"carouselLeft\">\n                <span class=\"pill\">Flow</span>\n\n                <div class=\"modeBar\" role=\"group\" aria-label=\"Portal tier modes\">\n                  <button id=\"modeQuickBtn\" class=\"modeBtn\" type=\"button\" aria-pressed=\"false\">Quick Start</button>\n                  <button id=\"modeSemiBtn\" class=\"modeBtn\" type=\"button\" aria-pressed=\"false\">Semi Pro</button>\n                  <button id=\"modeAdvBtn\" class=\"modeBtn\" type=\"button\" aria-pressed=\"false\">Advanced</button>\n                </div>\n                <span id=\"modeLabelPill\" class=\"modeTag\">mode: QUICK</span>\n\n                <span id=\"portalCounter\" class=\"carouselCounter\">0/0</span>\n                <span class=\"kbdHint\">← →</span>\n                <span class=\"kbdHint\">Enter</span>\n              </div>\n              <div class=\"carouselRight\">\n                <label class=\"srOnly\" for=\"portalJump\">Jump to portal</label>\n                <select id=\"portalJump\" class=\"jumpSelect\" aria-label=\"Jump to portal\"></select>\n                <button id=\"portalPrevBtn\" class=\"btn small\" type=\"button\" aria-label=\"Previous portal\">◀ Prev</button>\n                <button id=\"portalNextBtn\" class=\"btn small primary\" type=\"button\" aria-label=\"Next portal\">Next ▶</button>\n              </div>\n            </div>\n\n            <div id=\"portalStage\" class=\"carouselStage\" aria-label=\"Active portal stage\"></div>\n\n            <div class=\"carouselHintRow\">\n              <div id=\"portalDots\" class=\"carouselDots\" aria-label=\"Portal progress dots\"></div>\n              <div style=\"display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:flex-end\">\n                <span style=\"color:var(--muted);font-size:12px\">Tip: Click portal card to edit. Hooks append in the modal.</span>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"portalNote\">\n            Active portal: <span id=\"activePortalName\" class=\"pill\">None</span>\n            <span id=\"continuityStatusPill\" class=\"pill\" style=\"margin-left:8px\">continuity: DISENGAGED</span>\n            <span style=\"margin-left:8px;color:var(--muted);font-size:12px\">Tip: Import hook packs, then inject hooks into any portal.</span>\n          </div>\n\n          <div class=\"actions\" role=\"group\" aria-label=\"Editor actions\">\n            <button id=\"mintBtn\" class=\"btn primary\" type=\"button\">Mint Proof Receipt</button>\n            <button id=\"copyBtn\" class=\"btn\" type=\"button\">Copy Receipt</button>\n            <button id=\"exportBtn\" class=\"btn\" type=\"button\">Export Receipt</button>\n            <button id=\"clearEditorBtn\" class=\"btn danger\" type=\"button\" title=\"Clears ALL portal entries in the current prompt\">Clear Editor</button>\n            <button id=\"clearReceiptBtn\" class=\"btn danger\" type=\"button\" title=\"Clears only the current on-screen receipt (does not delete vault log)\">Clear Receipt</button>\n          </div>\n\n          <div class=\"srOnly\" id=\"ariaStatus\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n        </div>\n      </section>\n\n      <section class=\"card\" aria-label=\"Receipt viewer\">\n        <div class=\"cardHeader\" id=\"receiptHeader\">\n          <button id=\"receiptToggleBtn\" class=\"receiptToggleBtn\" type=\"button\" aria-expanded=\"true\" aria-controls=\"receiptBody\">\n            <span class=\"chev\" id=\"receiptChev\">▾</span>\n            <span>Three-Layer Music Receipt</span>\n          </button>\n\n          <div class=\"receiptHeaderRight\">\n            <button id=\"promptEntriesBtn\" class=\"btn secondary small\" type=\"button\" aria-haspopup=\"dialog\" title=\"Shows Layer 1 + Layer 2 only (ready to paste)\">\n              PROMPT ENTRIES COMPLETED\n            </button>\n            <div class=\"hint\">Paste Layer 1 + 2 into Suno/Udio • Keep Layer 3 as proof</div>\n          </div>\n        </div>\n\n        <div class=\"cardBody\" id=\"receiptBody\">\n          <pre id=\"receiptPre\" aria-label=\"Minted receipt output\">No receipt minted yet.</pre>\n          <textarea id=\"legacyCopyArea\" aria-hidden=\"true\"></textarea>\n        </div>\n      </section>\n    </main>\n\n    <footer class=\"footerZone\" role=\"contentinfo\">\n      <section id=\"logCard\" class=\"card hidden logCompact\" aria-label=\"Minted receipt log\">\n        <div class=\"cardHeader\">\n          <h2>Minted Proof Log (Local)</h2>\n          <div class=\"hint\">Compact • scroll • copy per entry</div>\n        </div>\n        <div class=\"cardBody\">\n          <div class=\"actions\" role=\"group\" aria-label=\"Log actions\" style=\"margin-top:0;margin-bottom:10px\">\n            <button id=\"refreshLogBtn\" class=\"btn\" type=\"button\">Refresh Log</button>\n            <button id=\"exportVaultBtn\" class=\"btn secondary\" type=\"button\">Export Vault JSON</button>\n            <button id=\"clearVaultBtn\" class=\"btn danger\" type=\"button\" title=\"Deletes local vault log from this browser/device only\">Clear Vault (Local)</button>\n          </div>\n\n          <div id=\"logViewport\" class=\"logViewport\" aria-label=\"Vault log viewport\">\n            <div style=\"color:var(--muted);font-size:12px\">No receipts logged yet.</div>\n          </div>\n        </div>\n      </section>\n\n      <div class=\"copyrightFooter\">Copyrights Acbeatz.com IP Property 2026</div>\n    </footer>\n  </div>\n\n  <!-- Portal + Hooks modal -->\n  <div id=\"modalBg\" class=\"modalBg\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"modalTitle\" aria-hidden=\"true\">\n    <div class=\"modal\">\n      <div class=\"modalTop\">\n        <div style=\"min-width:0\">\n          <div id=\"modalTitle\" class=\"t\">Portal Editor</div>\n          <div id=\"modalSub\" class=\"s\">Click a hook to append, or type your custom entry.</div>\n\n          <!-- UPDATE: Sub-header menu inside Portal Editor to switch hook packs -->\n          <div class=\"packBar\" aria-label=\"Hook pack selector inside Portal Editor\">\n            <span class=\"packLabel\">Hook Pack:</span>\n            <div id=\"modalPackChips\" class=\"packChips\" role=\"group\" aria-label=\"Imported JSON hook packs (click to select)\"></div>\n            <span id=\"modalActivePackPill\" class=\"packActivePill\">active: MERGED</span>\n          </div>\n\n          <!-- UPDATE #2: Continuity Lock controls (only shown on CONTINUITY_LOCK portal) -->\n          <div id=\"lockBar\" class=\"lockBar\" aria-label=\"Continuity lock controls\">\n            <span id=\"lockStatusPill\" class=\"lockPill\">DISENGAGED</span>\n            <button id=\"lockAddBtn\" class=\"btn secondary small\" type=\"button\" title=\"Adds (snapshots) GENRE + BPM + KEY into Continuity Lock and engages lock\">ADD LOCK (GENRE+BPM+KEY)</button>\n            <button id=\"lockEngageBtn\" class=\"btn small primary\" type=\"button\" title=\"Engage continuity lock using current lock snapshot\">ENGAGE</button>\n            <button id=\"lockDisengageBtn\" class=\"btn small danger\" type=\"button\" title=\"Disengage continuity lock (unlocks GENRE/BPM/KEY editing)\">DISENGAGE</button>\n          </div>\n        </div>\n\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\">\n          <button id=\"modalAppendSelectedHookBtn\" class=\"btn secondary\" type=\"button\" title=\"Appends the selected hook into this portal\">Append Selected Hook</button>\n          <button id=\"modalSaveBtn\" class=\"btn primary\" type=\"button\">Save Portal</button>\n          <button id=\"modalCloseBtn\" class=\"btn danger\" type=\"button\">Close</button>\n        </div>\n      </div>\n\n      <div class=\"modalBody\">\n        <div>\n          <div class=\"fieldLabel\">Portal content (manual typing allowed)</div>\n          <textarea id=\"modalText\" spellcheck=\"false\" aria-label=\"Portal content editor\"></textarea>\n        </div>\n\n        <div>\n          <div class=\"fieldLabel\">Imported hooks menu (bracketed strings)</div>\n          <div id=\"hookList\" class=\"hookList\" aria-label=\"Hooks list\">\n            <div style=\"color:var(--muted);font-size:12px\">No hooks imported yet. Use “Import Hooks JSON”.</div>\n          </div>\n          <div style=\"margin-top:10px;color:var(--muted);font-size:12px\">\n            Active hook: <span id=\"activeHookLabel\" class=\"pill\">None</span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Imports Manager modal -->\n  <div id=\"importsBg\" class=\"modalBg\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"importsTitle\" aria-hidden=\"true\">\n    <div class=\"modal\" style=\"max-width:980px\">\n      <div class=\"modalTop\">\n        <div>\n          <div id=\"importsTitle\" class=\"t\">Imports Manager — Hook Packs</div>\n          <div class=\"s\">Active hooks in the UI are the merged union of all imported JSON files.</div>\n        </div>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\">\n          <button id=\"importsMergeBtn\" class=\"btn secondary\" type=\"button\" title=\"Rebuild merged hooks from all imports\">Merge / Rebuild</button>\n          <button id=\"importsExportAllBtn\" class=\"btn secondary\" type=\"button\" title=\"Export all imported JSON files as a JSON string array for re-upload later\">Export All Imports</button>\n          <button id=\"importsClearAllBtn\" class=\"btn danger\" type=\"button\" title=\"Clear ALL imported JSON files and merged hooks from this device\">Clear All Imports</button>\n          <button id=\"importsCloseBtn\" class=\"btn danger\" type=\"button\">Close</button>\n        </div>\n      </div>\n\n      <div class=\"modalBody\" style=\"grid-template-columns:1fr\">\n        <div>\n          <div class=\"fieldLabel\">Active imports (this device)</div>\n\n          <div class=\"importsSummary\" aria-label=\"Imports summary\">\n            <span class=\"pill\">files: <span id=\"importsCountPill\">0</span></span>\n            <span class=\"pill\">merged hooks: <span id=\"importsMergedHooksPill\">0</span></span>\n            <span class=\"pill\">active in UI: <span id=\"importsActiveModePill\">MERGED</span></span>\n          </div>\n\n          <div style=\"margin-top:10px\" class=\"importsList\" id=\"importsList\" aria-label=\"Imported JSON files list\">\n            <div class=\"miniNote\">No imports yet. Use “Import Hooks JSON” to add one or more files.</div>\n          </div>\n\n          <div style=\"margin-top:10px\" class=\"miniNote\">\n            Export format is a JSON array of strings: each string is the original imported file text. Re-upload later to rebuild hook packs exactly.\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Prompt Entries Completed modal -->\n  <div id=\"promptEntriesBg\" class=\"modalBg\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"promptEntriesTitle\" aria-hidden=\"true\">\n    <div class=\"modal\" style=\"max-width:980px\">\n      <div class=\"modalTop\">\n        <div>\n          <div id=\"promptEntriesTitle\" class=\"t\">PROMPT ENTRIES COMPLETED</div>\n          <div class=\"s\">This window shows ONLY Layer 1 + Layer 2 (ready to paste). Layer 3 stays in the full receipt as proof.</div>\n        </div>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\">\n          <button id=\"promptEntriesCopyBtn\" class=\"btn secondary\" type=\"button\">Copy</button>\n          <button id=\"promptEntriesExportBtn\" class=\"btn secondary\" type=\"button\">Export</button>\n          <button id=\"promptEntriesClearBtn\" class=\"btn danger\" type=\"button\">Clear</button>\n          <button id=\"promptEntriesCloseBtn\" class=\"btn danger\" type=\"button\">Close</button>\n        </div>\n      </div>\n\n      <div class=\"modalBody\" style=\"grid-template-columns:1fr\">\n        <div>\n          <pre id=\"promptEntriesPre\" aria-label=\"Prompt entries completed output\">No prompt entries completed yet. Mint a receipt first.</pre>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Toast -->\n  <div class=\"toastWrap\" aria-hidden=\"false\">\n    <div id=\"toast\" class=\"toast\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n      <div class=\"msg\" id=\"toastMsg\">Ready.</div>\n      <div class=\"tag\" id=\"toastTag\">MH8</div>\n    </div>\n  </div>\n\n  <script>\n  (function () {\n    \"use strict\";\n\n    // =========================\n    // MUSIC PORTALS (schema)\n    // =========================\n    // NOTE: All portals are text so hooks can be injected anywhere.\n    var PORTAL_KEYS = [\n      \"MODEL_BEHAVIOR_CONTROL\",\n      \"LYRICS\",\n      \"GENRE\",\n      \"MOOD\",\n      \"ENERGY\",\n      \"VOCAL_INTENSITY\",\n      \"ERA_INFLUENCE\",\n      \"BPM\",\n      \"KEY_SIGNATURE\",\n      \"BACKING_VOX\",\n      \"DRUMS\",\n      \"GUITAR\",\n      \"BASS\",\n      \"PIANO_KEYS\",\n      \"SYNTH\",\n      \"PERCUSSION\",\n      \"DYNAMICS\",\n      \"MIX\",\n      \"MASTER\",\n      \"EXTRA_HOOKS\",\n      \"NOTES\",\n      \"CONTINUITY_LOCK\",\n      \"SHA256_ID\"\n    ];\n\n    // Tier flows (flow-only; all portals still exist, mint/export/clear exactly the same)\n    var QUICK_KEYS = [\"MODEL_BEHAVIOR_CONTROL\",\"LYRICS\",\"GENRE\",\"MOOD\",\"ENERGY\",\"VOCAL_INTENSITY\",\"SHA256_ID\"];\n    var SEMI_KEYS  = [\"MODEL_BEHAVIOR_CONTROL\",\"LYRICS\",\"GENRE\",\"MOOD\",\"ENERGY\",\"VOCAL_INTENSITY\",\"ERA_INFLUENCE\",\"BPM\",\"KEY_SIGNATURE\",\"BACKING_VOX\",\"DRUMS\",\"BASS\",\"GUITAR\",\"SYNTH\",\"MIX\",\"MASTER\",\"EXTRA_HOOKS\",\"CONTINUITY_LOCK\",\"SHA256_ID\"];\n    var ADV_KEYS   = PORTAL_KEYS.slice(0);\n\n    // Optional: default copy block for NOTES (kept empty by default)\n    var DEFAULT_NOTES = \"\";\n\n    // =========================\n    // DOM\n    // =========================\n    var platformSelect = document.getElementById(\"platformSelect\");\n    var themeBtn = document.getElementById(\"themeBtn\");\n    var logBtn = document.getElementById(\"logBtn\");\n    var importBtn = document.getElementById(\"importBtn\");\n    var importMgrBtn = document.getElementById(\"importMgrBtn\");\n    var importFile = document.getElementById(\"importFile\");\n\n    var tablist = document.getElementById(\"tablist\");\n    var addTabBtn = document.getElementById(\"addTabBtn\");\n\n    var portalStage = document.getElementById(\"portalStage\");\n    var portalDots = document.getElementById(\"portalDots\");\n    var portalPrevBtn = document.getElementById(\"portalPrevBtn\");\n    var portalNextBtn = document.getElementById(\"portalNextBtn\");\n    var portalJump = document.getElementById(\"portalJump\");\n    var portalCounter = document.getElementById(\"portalCounter\");\n\n    var modeQuickBtn = document.getElementById(\"modeQuickBtn\");\n    var modeSemiBtn = document.getElementById(\"modeSemiBtn\");\n    var modeAdvBtn = document.getElementById(\"modeAdvBtn\");\n    var modeLabelPill = document.getElementById(\"modeLabelPill\");\n\n    var activePortalName = document.getElementById(\"activePortalName\");\n    var continuityStatusPill = document.getElementById(\"continuityStatusPill\");\n\n    var mintBtn = document.getElementById(\"mintBtn\");\n    var copyBtn = document.getElementById(\"copyBtn\");\n    var exportBtn = document.getElementById(\"exportBtn\");\n    var clearReceiptBtn = document.getElementById(\"clearReceiptBtn\");\n    var clearEditorBtn = document.getElementById(\"clearEditorBtn\");\n\n    var receiptPre = document.getElementById(\"receiptPre\");\n    var legacyCopyArea = document.getElementById(\"legacyCopyArea\");\n\n    var receiptToggleBtn = document.getElementById(\"receiptToggleBtn\");\n    var receiptChev = document.getElementById(\"receiptChev\");\n    var receiptBody = document.getElementById(\"receiptBody\");\n\n    var promptEntriesBtn = document.getElementById(\"promptEntriesBtn\");\n    var promptEntriesBg = document.getElementById(\"promptEntriesBg\");\n    var promptEntriesCloseBtn = document.getElementById(\"promptEntriesCloseBtn\");\n    var promptEntriesCopyBtn = document.getElementById(\"promptEntriesCopyBtn\");\n    var promptEntriesClearBtn = document.getElementById(\"promptEntriesClearBtn\");\n    var promptEntriesExportBtn = document.getElementById(\"promptEntriesExportBtn\");\n    var promptEntriesPre = document.getElementById(\"promptEntriesPre\");\n\n    var logCard = document.getElementById(\"logCard\");\n    var refreshLogBtn = document.getElementById(\"refreshLogBtn\");\n    var exportVaultBtn = document.getElementById(\"exportVaultBtn\");\n    var clearVaultBtn = document.getElementById(\"clearVaultBtn\");\n    var logViewport = document.getElementById(\"logViewport\");\n\n    var ariaStatus = document.getElementById(\"ariaStatus\");\n    var toast = document.getElementById(\"toast\");\n    var toastMsg = document.getElementById(\"toastMsg\");\n    var toastTag = document.getElementById(\"toastTag\");\n\n    var modalBg = document.getElementById(\"modalBg\");\n    var modalTitle = document.getElementById(\"modalTitle\");\n    var modalSub = document.getElementById(\"modalSub\");\n    var modalText = document.getElementById(\"modalText\");\n    var modalAppendSelectedHookBtn = document.getElementById(\"modalAppendSelectedHookBtn\");\n    var modalSaveBtn = document.getElementById(\"modalSaveBtn\");\n    var modalCloseBtn = document.getElementById(\"modalCloseBtn\");\n\n    var hookList = document.getElementById(\"hookList\");\n    var activeHookLabel = document.getElementById(\"activeHookLabel\");\n\n    // UPDATE: Portal Editor pack menu DOM\n    var modalPackChips = document.getElementById(\"modalPackChips\");\n    var modalActivePackPill = document.getElementById(\"modalActivePackPill\");\n\n    // UPDATE #2: Continuity lock modal controls DOM\n    var lockBar = document.getElementById(\"lockBar\");\n    var lockStatusPill = document.getElementById(\"lockStatusPill\");\n    var lockAddBtn = document.getElementById(\"lockAddBtn\");\n    var lockEngageBtn = document.getElementById(\"lockEngageBtn\");\n    var lockDisengageBtn = document.getElementById(\"lockDisengageBtn\");\n\n    var importsBg = document.getElementById(\"importsBg\");\n    var importsCloseBtn = document.getElementById(\"importsCloseBtn\");\n    var importsMergeBtn = document.getElementById(\"importsMergeBtn\");\n    var importsExportAllBtn = document.getElementById(\"importsExportAllBtn\");\n    var importsClearAllBtn = document.getElementById(\"importsClearAllBtn\");\n    var importsList = document.getElementById(\"importsList\");\n    var importsCountPill = document.getElementById(\"importsCountPill\");\n    var importsMergedHooksPill = document.getElementById(\"importsMergedHooksPill\");\n    var importsActiveModePill = document.getElementById(\"importsActiveModePill\");\n\n    // =========================\n    // STATE\n    // =========================\n    var state = {\n      platform: \"suno\",\n      theme: \"dark\",\n      showLog: false,\n      receiptText: \"\",\n      promptEntriesText: \"\",\n      receiptOpen: true,\n\n      tabs: [],\n      activeId: null,\n\n      hooks: [],\n      selectedHook: \"\",\n      activePortalKey: \"\",\n      modalPortalKey: \"\",\n\n      imports: [], // [{id, name, created_utc, raw_text, hooks_count}]\n      portal_mode: \"quick\", // quick | semi | adv\n\n      // UPDATE: Portal Editor pack selector state\n      modal_hook_pack_mode: \"merged\", // merged | single\n      modal_hook_pack_id: \"\" // import.id when single\n    };\n\n    // =========================\n    // Storage safe helpers\n    // =========================\n    function storageGet(key, fallback) {\n      try {\n        var v = localStorage.getItem(key);\n        if (v === null || v === undefined) return fallback;\n        return v;\n      } catch (e) { return fallback; }\n    }\n    function storageSet(key, value) { try { localStorage.setItem(key, value); return true; } catch (e) { return false; } }\n    function storageRemove(key) { try { localStorage.removeItem(key); return true; } catch (e) { return false; } }\n    function nowISO() { return new Date().toISOString(); }\n\n    // =========================\n    // Toast / ARIA\n    // =========================\n    var toastTimer = null;\n    function announce(msg) { ariaStatus.textContent = msg; }\n    function showToast(msg, tag) {\n      if (tag) toastTag.textContent = tag;\n      toastMsg.textContent = msg;\n      toast.className = \"toast show\";\n      announce(msg);\n      if (toastTimer) clearTimeout(toastTimer);\n      toastTimer = setTimeout(function () { toast.className = \"toast\"; }, 1800);\n    }\n\n    // =========================\n    // Theme\n    // =========================\n    function applyTheme(theme) {\n      state.theme = theme;\n      document.documentElement.setAttribute(\"data-theme\", theme);\n      var isDark = (theme === \"dark\");\n      themeBtn.setAttribute(\"aria-pressed\", isDark ? \"true\" : \"false\");\n      themeBtn.textContent = isDark ? \"Light Mode\" : \"Dark Mode\";\n      storageSet(\"mh8_music_theme\", theme);\n    }\n    function toggleTheme() {\n      applyTheme(state.theme === \"dark\" ? \"light\" : \"dark\");\n      showToast(\"Theme switched to \" + (state.theme === \"dark\" ? \"Dark\" : \"Light\") + \".\", \"THEME\");\n    }\n\n    // =========================\n    // Vault\n    // =========================\n    function getVault() {\n      var raw = storageGet(\"mh8_music_vault\", \"[]\");\n      try {\n        var arr = JSON.parse(raw);\n        if (Object.prototype.toString.call(arr) !== \"[object Array]\") return [];\n        return arr;\n      } catch (e) { return []; }\n    }\n    function setVault(arr) { return storageSet(\"mh8_music_vault\", JSON.stringify(arr)); }\n\n    function esc(s) {\n      s = String(s === undefined || s === null ? \"\" : s);\n      return s.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&#039;\");\n    }\n\n    function renderVaultEntry(entry, indexFromStart) {\n      var ts = entry && entry.created_utc ? entry.created_utc : \"\";\n      var sha = entry && entry.sha256 ? entry.sha256 : \"\";\n      var platform = entry && entry.payload && entry.payload.platform ? entry.payload.platform : \"\";\n      var name = (entry && entry.payload && entry.payload.prompt_name) ? entry.payload.prompt_name : (\"Prompt\");\n\n      var fullText =\n        (entry && entry.full_receipt) ? entry.full_receipt :\n        (entry && entry.receipt_text) ? (entry.receipt_text + \"\\n\\n\" + JSON.stringify(entry, null, 2)) :\n        JSON.stringify(entry, null, 2);\n\n      var id = \"logcopy_\" + String(indexFromStart);\n\n      return ''\n        + '<div class=\"logEntry\">'\n        +   '<div class=\"logMeta\">'\n        +     '<div class=\"left\">'\n        +       '<span class=\"pill\">' + esc(name) + '</span>'\n        +       '<span class=\"pill\">platform:' + esc(platform) + '</span>'\n        +       '<span class=\"pill\">ts:' + esc(ts) + '</span>'\n        +       '<span class=\"pill\">sha:' + esc(sha.slice(0,12)) + '…</span>'\n        +     '</div>'\n        +     '<div class=\"logActions\">'\n        +       '<button class=\"btn small\" type=\"button\" data-copyid=\"' + esc(id) + '\">Copy Entry</button>'\n        +     '</div>'\n        +   '</div>'\n        +   '<pre class=\"logPre\" id=\"' + esc(id) + '\">' + esc(fullText) + '</pre>'\n        + '</div>';\n    }\n\n    function bindLogCopyButtons() {\n      var btns = logViewport.querySelectorAll(\"button[data-copyid]\");\n      for (var i = 0; i < btns.length; i++) {\n        btns[i].onclick = function () {\n          var id = this.getAttribute(\"data-copyid\");\n          var el = document.getElementById(id);\n          if (!el) return;\n          copyAnyText(el.textContent || \"\");\n        };\n      }\n    }\n\n    function renderVault() {\n      var vault = getVault();\n      if (!vault.length) {\n        logViewport.innerHTML = '<div style=\"color:var(--muted);font-size:12px\">No receipts logged yet.</div>';\n        return;\n      }\n      var out = [];\n      for (var i = vault.length - 1; i >= 0; i--) out.push(renderVaultEntry(vault[i], i));\n      logViewport.innerHTML = out.join(\"\");\n      bindLogCopyButtons();\n    }\n\n    // =========================\n    // Tabs\n    // =========================\n    function uid() { return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); }\n\n    function makeEmptyPortals() {\n      var obj = {};\n      for (var i = 0; i < PORTAL_KEYS.length; i++) obj[PORTAL_KEYS[i]] = \"\";\n      obj.NOTES = DEFAULT_NOTES;\n\n      // UPDATE #2: Default continuity lock state visible + receipt-includable\n      obj.CONTINUITY_LOCK = \"DISENGAGED\";\n\n      // UPDATE #3: SHA256 ID portal default empty\n      if (typeof obj.SHA256_ID === \"undefined\") obj.SHA256_ID = \"\";\n\n      return obj;\n    }\n\n    function loadTabs() {\n      var raw = storageGet(\"mh8_music_tabs\", \"\");\n      var rawActive = storageGet(\"mh8_music_active\", \"\");\n      var tabs = null;\n\n      if (raw) { try { tabs = JSON.parse(raw); } catch (e) { tabs = null; } }\n\n      if (tabs && Object.prototype.toString.call(tabs) === \"[object Array]\" && tabs.length) {\n        var migrated = [];\n        for (var i = 0; i < tabs.length; i++) {\n          var t = tabs[i];\n          var portals = (t && t.portals) ? t.portals : makeEmptyPortals();\n\n          // UPDATE #2: Ensure continuity portal exists on older saves\n          if (portals && typeof portals.CONTINUITY_LOCK === \"undefined\") portals.CONTINUITY_LOCK = \"DISENGAGED\";\n\n          // UPDATE #3: Ensure SHA256_ID exists on older saves\n          if (portals && typeof portals.SHA256_ID === \"undefined\") portals.SHA256_ID = \"\";\n\n          migrated.push({\n            id: String(t.id || uid()),\n            name: String(t.name || (\"Prompt \" + (i + 1))),\n            portals: portals,\n\n            // UPDATE #2: continuity lock meta (additive; safe migration)\n            continuity_lock: (t && t.continuity_lock) ? t.continuity_lock : null\n          });\n        }\n        tabs = migrated;\n      }\n\n      if (!tabs || Object.prototype.toString.call(tabs) !== \"[object Array]\" || !tabs.length) {\n        tabs = [{ id: \"1\", name: \"Prompt 1\", portals: makeEmptyPortals(), continuity_lock: null }];\n        rawActive = \"1\";\n      }\n\n      state.tabs = tabs;\n      state.activeId = (rawActive && findTab(rawActive)) ? String(rawActive) : String(tabs[0].id);\n      persistTabs();\n    }\n\n    function persistTabs() {\n      storageSet(\"mh8_music_tabs\", JSON.stringify(state.tabs));\n      storageSet(\"mh8_music_active\", String(state.activeId));\n    }\n\n    function findTab(id) {\n      for (var i = 0; i < state.tabs.length; i++) {\n        if (String(state.tabs[i].id) === String(id)) return state.tabs[i];\n      }\n      return null;\n    }\n\n    function indexOfTab(id) {\n      for (var i = 0; i < state.tabs.length; i++) if (String(state.tabs[i].id) === String(id)) return i;\n      return 0;\n    }\n\n    function prevent(e) { if (!e) return; if (e.preventDefault) e.preventDefault(); e.returnValue = false; }\n\n    function setActiveTab(id, focusTabBtn) {\n      if (!findTab(id)) return;\n      state.activeId = String(id);\n      persistTabs();\n      renderTabs();\n      renderPortals();\n      if (focusTabBtn) {\n        var btn = document.getElementById(\"tabbtn_\" + state.activeId);\n        if (btn && btn.focus) btn.focus();\n      }\n      showToast(\"Active: \" + (findTab(state.activeId).name), \"TABS\");\n    }\n\n    function addTab() {\n      var id = uid();\n      var name = \"Prompt \" + (state.tabs.length + 1);\n      state.tabs.push({ id: id, name: name, portals: makeEmptyPortals(), continuity_lock: null });\n      setActiveTab(id, true);\n      showToast(\"New prompt created: \" + name + \".\", \"TABS\");\n    }\n\n    function deleteTab(id) {\n      if (state.tabs.length <= 1) { showToast(\"Cannot delete the last remaining prompt.\", \"TABS\"); return; }\n      var idx = -1;\n      for (var i = 0; i < state.tabs.length; i++) if (String(state.tabs[i].id) === String(id)) { idx = i; break; }\n      if (idx < 0) return;\n\n      var wasActive = (String(state.activeId) === String(id));\n      var name = state.tabs[idx].name;\n\n      state.tabs.splice(idx, 1);\n      if (wasActive) {\n        var newActive = state.tabs[Math.max(0, idx - 1)].id;\n        state.activeId = String(newActive);\n      }\n      persistTabs();\n      renderTabs();\n      renderPortals();\n      showToast(\"Deleted: \" + name + \".\", \"TABS\");\n    }\n\n    function renderTabs() {\n      while (tablist.firstChild) tablist.removeChild(tablist.firstChild);\n\n      for (var i = 0; i < state.tabs.length; i++) (function (tab) {\n        var wrap = document.createElement(\"div\");\n        wrap.className = \"tabWrap\";\n\n        var btn = document.createElement(\"button\");\n        btn.type = \"button\";\n        btn.className = \"tab\";\n        btn.id = \"tabbtn_\" + tab.id;\n        btn.setAttribute(\"role\", \"tab\");\n        btn.setAttribute(\"tabindex\", String(tab.id === state.activeId ? \"0\" : \"-1\"));\n        btn.setAttribute(\"aria-selected\", tab.id === state.activeId ? \"true\" : \"false\");\n        btn.textContent = tab.name;\n        btn.onclick = function () { setActiveTab(tab.id, true); };\n\n        btn.onkeydown = function (e) {\n          e = e || window.event;\n          var key = e.key || e.keyCode;\n          var idx = indexOfTab(tab.id);\n          if (key === \"ArrowLeft\" || key === 37) {\n            prevent(e);\n            var prev = (idx - 1 + state.tabs.length) % state.tabs.length;\n            setActiveTab(state.tabs[prev].id, true);\n          } else if (key === \"ArrowRight\" || key === 39) {\n            prevent(e);\n            var next = (idx + 1) % state.tabs.length;\n            setActiveTab(state.tabs[next].id, true);\n          } else if (key === \"Home\" || key === 36) {\n            prevent(e); setActiveTab(state.tabs[0].id, true);\n          } else if (key === \"End\" || key === 35) {\n            prevent(e); setActiveTab(state.tabs[state.tabs.length - 1].id, true);\n          }\n        };\n\n        var del = document.createElement(\"button\");\n        del.type = \"button\";\n        del.className = \"tabDel\";\n        del.setAttribute(\"aria-label\", \"Delete \" + tab.name);\n        del.title = \"Delete this prompt\";\n        del.innerHTML = \"✕\";\n        del.onclick = function (ev) {\n          if (ev && ev.preventDefault) ev.preventDefault();\n          deleteTab(tab.id);\n        };\n\n        wrap.appendChild(btn);\n        wrap.appendChild(del);\n        tablist.appendChild(wrap);\n      })(state.tabs[i]);\n    }\n\n    // =========================\n    // UPDATE #2: Continuity Lock (per-tab)\n    // =========================\n    function getTabContinuity(tab) {\n      if (!tab) return null;\n      if (!tab.continuity_lock || Object.prototype.toString.call(tab.continuity_lock) !== \"[object Object]\") {\n        tab.continuity_lock = {\n          engaged: false,\n          created_utc: \"\",\n          genre: \"\",\n          bpm: \"\",\n          key_signature: \"\"\n        };\n      }\n      if (typeof tab.continuity_lock.engaged !== \"boolean\") tab.continuity_lock.engaged = false;\n      tab.continuity_lock.genre = String(tab.continuity_lock.genre || \"\");\n      tab.continuity_lock.bpm = String(tab.continuity_lock.bpm || \"\");\n      tab.continuity_lock.key_signature = String(tab.continuity_lock.key_signature || \"\");\n      tab.continuity_lock.created_utc = String(tab.continuity_lock.created_utc || \"\");\n      return tab.continuity_lock;\n    }\n\n    function formatContinuityLockText(lock) {\n      lock = lock || { engaged:false, created_utc:\"\", genre:\"\", bpm:\"\", key_signature:\"\" };\n      if (!lock.engaged) return \"DISENGAGED\";\n      var out = [];\n      out.push(\"ENGAGED\");\n      out.push(\"LOCKED_UTC: \" + String(lock.created_utc || \"\"));\n      out.push(\"GENRE: \" + String(lock.genre || \"\").trim());\n      out.push(\"BPM: \" + String(lock.bpm || \"\").trim());\n      out.push(\"KEY_SIGNATURE: \" + String(lock.key_signature || \"\").trim());\n      return out.join(\"\\n\").trim();\n    }\n\n    function setContinuityLockPortalFromMeta(tab) {\n      if (!tab) return;\n      var lock = getTabContinuity(tab);\n      if (!tab.portals) tab.portals = makeEmptyPortals();\n      tab.portals.CONTINUITY_LOCK = formatContinuityLockText(lock);\n    }\n\n    function applyContinuityLockToPortals(tab) {\n      if (!tab || !tab.portals) return;\n      var lock = getTabContinuity(tab);\n      if (!lock.engaged) return;\n\n      // Enforce locked values into portals\n      tab.portals.GENRE = String(lock.genre || \"\");\n      tab.portals.BPM = String(lock.bpm || \"\");\n      tab.portals.KEY_SIGNATURE = String(lock.key_signature || \"\");\n      setContinuityLockPortalFromMeta(tab);\n    }\n\n    function continuityIsEngaged(tab) {\n      var lock = getTabContinuity(tab);\n      return !!(lock && lock.engaged);\n    }\n\n    function snapshotContinuityFromCurrent(tab) {\n      if (!tab || !tab.portals) return;\n      var lock = getTabContinuity(tab);\n      lock.created_utc = nowISO();\n      lock.genre = String(tab.portals.GENRE || \"\");\n      lock.bpm = String(tab.portals.BPM || \"\");\n      lock.key_signature = String(tab.portals.KEY_SIGNATURE || \"\");\n      lock.engaged = true;\n      applyContinuityLockToPortals(tab);\n      persistTabs();\n    }\n\n    function disengageContinuity(tab) {\n      if (!tab) return;\n      var lock = getTabContinuity(tab);\n      lock.engaged = false;\n      setContinuityLockPortalFromMeta(tab);\n      persistTabs();\n    }\n\n    function engageContinuity(tab) {\n      if (!tab) return;\n      var lock = getTabContinuity(tab);\n\n      // UPDATE #4: ALWAYS capture CURRENT portal inputs at ENGAGE time\n      // This guarantees GENRE/BPM/KEY_SIGNATURE are recognized and printed in receipts (not blank).\n      if (!tab.portals) tab.portals = makeEmptyPortals();\n      lock.genre = String(tab.portals.GENRE || \"\");\n      lock.bpm = String(tab.portals.BPM || \"\");\n      lock.key_signature = String(tab.portals.KEY_SIGNATURE || \"\");\n\n      if (!lock.created_utc) lock.created_utc = nowISO();\n      lock.engaged = true;\n      applyContinuityLockToPortals(tab);\n      persistTabs();\n    }\n\n    function renderContinuityStatusPill() {\n      if (!continuityStatusPill) return;\n      var tab = findTab(state.activeId);\n      if (!tab) { continuityStatusPill.textContent = \"continuity: DISENGAGED\"; continuityStatusPill.className = \"pill\"; return; }\n      var engaged = continuityIsEngaged(tab);\n      continuityStatusPill.textContent = \"continuity: \" + (engaged ? \"ENGAGED\" : \"DISENGAGED\");\n      continuityStatusPill.className = \"pill\" + (engaged ? \" locked\" : \"\");\n    }\n\n    // =========================\n    // Flow mode helpers\n    // =========================\n    function keysForMode(mode) {\n      mode = String(mode || \"\").toLowerCase();\n      if (mode === \"semi\") return SEMI_KEYS.slice(0);\n      if (mode === \"adv\") return ADV_KEYS.slice(0);\n      return QUICK_KEYS.slice(0);\n    }\n    function normalizeActivePortalForMode() {\n      var keys = keysForMode(state.portal_mode);\n      for (var i = 0; i < keys.length; i++) if (String(keys[i]) === String(state.activePortalKey)) return;\n      state.activePortalKey = keys.length ? keys[0] : \"LYRICS\";\n    }\n    function renderModeButtons() {\n      var m = String(state.portal_mode || \"quick\");\n      var isQ = (m === \"quick\"), isS = (m === \"semi\"), isA = (m === \"adv\");\n\n      modeQuickBtn.className = \"modeBtn\" + (isQ ? \" active\" : \"\");\n      modeSemiBtn.className  = \"modeBtn\" + (isS ? \" active\" : \"\");\n      modeAdvBtn.className   = \"modeBtn\" + (isA ? \" active\" : \"\");\n\n      modeQuickBtn.setAttribute(\"aria-pressed\", isQ ? \"true\" : \"false\");\n      modeSemiBtn.setAttribute(\"aria-pressed\",  isS ? \"true\" : \"false\");\n      modeAdvBtn.setAttribute(\"aria-pressed\",   isA ? \"true\" : \"false\");\n\n      modeLabelPill.textContent = \"mode: \" + (isQ ? \"QUICK\" : isS ? \"SEMI\" : \"ADV\");\n    }\n    function setPortalMode(mode, announceIt) {\n      mode = String(mode || \"\").toLowerCase();\n      if (mode !== \"quick\" && mode !== \"semi\" && mode !== \"adv\") mode = \"quick\";\n      state.portal_mode = mode;\n      storageSet(\"mh8_music_portal_mode\", mode);\n      normalizeActivePortalForMode();\n      renderModeButtons();\n      renderPortals();\n      if (announceIt) showToast(\"Flow mode: \" + (mode === \"quick\" ? \"Quick Start\" : mode === \"semi\" ? \"Semi Pro\" : \"Advanced Full Stack\") + \".\", \"FLOW\");\n    }\n\n    // =========================\n    // Portals render + edit (carousel)\n    // =========================\n    function portalIndexFromKey(key) {\n      var keys = keysForMode(state.portal_mode);\n      for (var i = 0; i < keys.length; i++) if (String(keys[i]) === String(key)) return i;\n      return 0;\n    }\n    function setActivePortalByIndex(idx, announceIt) {\n      var keys = keysForMode(state.portal_mode);\n      idx = Number(idx);\n      if (isNaN(idx)) idx = 0;\n      if (idx < 0) idx = 0;\n      if (idx >= keys.length) idx = keys.length - 1;\n      state.activePortalKey = keys[idx];\n      renderPortals();\n      if (announceIt) showToast(\"Portal: [\" + state.activePortalKey + \"].\", \"PORTAL\");\n    }\n    function goPrevPortal() { setActivePortalByIndex(portalIndexFromKey(state.activePortalKey) - 1, true); }\n    function goNextPortal() { setActivePortalByIndex(portalIndexFromKey(state.activePortalKey) + 1, true); }\n\n    function prettyPortalLabel(key) {\n      var map = {\n        MODEL_BEHAVIOR_CONTROL: \"MODEL BEHAVIOR CONTROL\",\n        LYRICS: \"LYRICS\",\n        GENRE: \"GENRE\",\n        MOOD: \"MOOD\",\n        ENERGY: \"ENERGY\",\n        VOCAL_INTENSITY: \"VOCAL INTENSITY\",\n        ERA_INFLUENCE: \"ERA / INFLUENCE\",\n        BPM: \"BPM\",\n        KEY_SIGNATURE: \"KEY SIGNATURE\",\n        BACKING_VOX: \"BACKING VOX\",\n        DRUMS: \"DRUMS\",\n        GUITAR: \"GUITAR\",\n        BASS: \"BASS\",\n        PIANO_KEYS: \"PIANO / KEYS\",\n        SYNTH: \"SYNTH\",\n        PERCUSSION: \"PERCUSSION\",\n        DYNAMICS: \"DYNAMICS\",\n        MIX: \"MIX\",\n        MASTER: \"MASTER\",\n        EXTRA_HOOKS: \"EXTRA HOOKS\",\n        NOTES: \"NOTES\",\n        CONTINUITY_LOCK: \"CONTINUITY LOCK\",\n        SHA256_ID: \"SHA256 ID\"\n      };\n      return map[key] || key;\n    }\n\n    function renderPortals() {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      // UPDATE #2: enforce lock into visible portals (no drift)\n      applyContinuityLockToPortals(tab);\n\n      // UPDATE #3: ensure SHA256_ID exists\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      normalizeActivePortalForMode();\n      renderModeButtons();\n\n      var keys = keysForMode(state.portal_mode);\n      if (!state.activePortalKey) state.activePortalKey = keys.length ? keys[0] : \"LYRICS\";\n      var idx = portalIndexFromKey(state.activePortalKey);\n      var key = keys[idx];\n      state.activePortalKey = key;\n\n      while (portalJump.firstChild) portalJump.removeChild(portalJump.firstChild);\n      for (var i = 0; i < keys.length; i++) {\n        var opt = document.createElement(\"option\");\n        opt.value = String(i);\n        opt.textContent = (i + 1) + \". [\" + prettyPortalLabel(keys[i]) + \"]\";\n        if (i === idx) opt.selected = true;\n        portalJump.appendChild(opt);\n      }\n\n      portalCounter.textContent = String(idx + 1) + \"/\" + String(keys.length);\n\n      while (portalDots.firstChild) portalDots.removeChild(portalDots.firstChild);\n      for (i = 0; i < keys.length; i++) (function (dotIndex) {\n        var d = document.createElement(\"button\");\n        d.type = \"button\";\n        d.className = \"dot\" + (dotIndex === idx ? \" active\" : \"\");\n        d.setAttribute(\"aria-label\", \"Go to portal \" + (dotIndex + 1));\n        d.onclick = function () { setActivePortalByIndex(dotIndex, false); };\n        portalDots.appendChild(d);\n      })(i);\n\n      while (portalStage.firstChild) portalStage.removeChild(portalStage.firstChild);\n\n      var val = (tab.portals && tab.portals[key]) ? String(tab.portals[key]) : \"\";\n\n      var portal = document.createElement(\"div\");\n\n      // UPDATE #2: mark locked portals when continuity lock engaged\n      var engaged = continuityIsEngaged(tab);\n      var isLockedPortal = engaged && (key === \"GENRE\" || key === \"BPM\" || key === \"KEY_SIGNATURE\");\n      portal.className = \"portal active\" + (isLockedPortal ? \" locked\" : \"\");\n      portal.setAttribute(\"role\", \"button\");\n      portal.setAttribute(\"tabindex\", \"0\");\n      portal.setAttribute(\"aria-label\", \"Portal \" + key + \". Click to edit.\");\n\n      var head = document.createElement(\"div\");\n      head.className = \"pHead\";\n\n      var title = document.createElement(\"div\");\n      title.className = \"pTitle\";\n      title.textContent = \"[\" + prettyPortalLabel(key) + \"]\";\n\n      var badge = document.createElement(\"div\");\n      badge.className = \"pBadge\";\n      if (isLockedPortal) badge.textContent = \"LOCKED\";\n      else badge.textContent = val ? (String(val.length) + \" chars\") : \"empty\";\n\n      head.appendChild(title);\n      head.appendChild(badge);\n\n      var body = document.createElement(\"div\");\n      body.className = \"pValue\";\n      if (!val) body.innerHTML = '<span class=\"pEmpty\">Click to add… (type or assign hooks)</span>';\n      else body.textContent = val;\n\n      portal.appendChild(head);\n      portal.appendChild(body);\n\n      portal.onclick = function () { openPortalModal(key); };\n      portal.onkeydown = function (e) {\n        e = e || window.event;\n        var k = e.key || e.keyCode;\n        if (k === \"Enter\" || k === \" \" || k === 13 || k === 32) { prevent(e); openPortalModal(key); }\n      };\n\n      portalStage.appendChild(portal);\n\n      activePortalName.textContent = \"[\" + prettyPortalLabel(key) + \"]\";\n\n      portalPrevBtn.disabled = (idx <= 0);\n      portalNextBtn.disabled = (idx >= keys.length - 1);\n\n      // UPDATE #2: continuity status in the editor footer row\n      renderContinuityStatusPill();\n    }\n\n    function updatePortalValue(portalKey, newValue) {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      if (!tab.portals) tab.portals = makeEmptyPortals();\n\n      // UPDATE #2: block updates to locked fields when engaged\n      if (continuityIsEngaged(tab) && (portalKey === \"GENRE\" || portalKey === \"BPM\" || portalKey === \"KEY_SIGNATURE\")) {\n        showToast(\"Continuity lock ENGAGED. Editing is locked for \" + portalKey + \". Disengage to edit.\", \"LOCK\");\n        applyContinuityLockToPortals(tab);\n        persistTabs();\n        renderPortals();\n        return;\n      }\n\n      tab.portals[portalKey] = String(newValue || \"\");\n\n      // UPDATE #2: keep continuity lock portal text in sync with meta (always)\n      if (portalKey === \"CONTINUITY_LOCK\") {\n        var lock = getTabContinuity(tab);\n        var txt = String(tab.portals.CONTINUITY_LOCK || \"\").trim().toUpperCase();\n        if (txt.indexOf(\"ENGAGED\") === 0) {\n          // If user manually typed ENGAGED, treat as engage (without snapshot change)\n          lock.engaged = true;\n          if (!lock.created_utc) lock.created_utc = nowISO();\n          setContinuityLockPortalFromMeta(tab);\n        } else if (txt.indexOf(\"DISENGAGED\") === 0 || txt === \"\") {\n          lock.engaged = false;\n          setContinuityLockPortalFromMeta(tab);\n        }\n      }\n\n      persistTabs();\n      renderPortals();\n    }\n\n    // =========================\n    // Modal\n    // =========================\n    var lastFocusEl = null;\n    var lastFocusElImports = null;\n    var lastFocusElPromptEntries = null;\n\n    // UPDATE: helpers for pack selection inside Portal Editor\n    function findImportById(id) {\n      for (var i = 0; i < state.imports.length; i++) if (String(state.imports[i].id) === String(id)) return state.imports[i];\n      return null;\n    }\n    function safeImportName(imp) {\n      var n = imp && imp.name ? String(imp.name) : \"import\";\n      if (n.length > 36) n = n.slice(0, 33) + \"…\";\n      return n;\n    }\n    function setModalHookPack(mode, id, announceIt) {\n      mode = String(mode || \"merged\");\n      if (mode !== \"merged\" && mode !== \"single\") mode = \"merged\";\n\n      if (mode === \"single\") {\n        var imp = findImportById(id);\n        if (!imp) mode = \"merged\";\n      }\n\n      state.modal_hook_pack_mode = mode;\n      state.modal_hook_pack_id = (mode === \"single\") ? String(id || \"\") : \"\";\n\n      renderHookPackMenu();\n      renderHookList();\n\n      if (announceIt) {\n        if (state.modal_hook_pack_mode === \"merged\") showToast(\"Hook pack set to MERGED (all).\", \"HOOKS\");\n        else {\n          var imp2 = findImportById(state.modal_hook_pack_id);\n          showToast(\"Hook pack selected: \" + (imp2 ? safeImportName(imp2) : \"import\") + \".\", \"HOOKS\");\n        }\n      }\n    }\n    function renderHookPackMenu() {\n      if (!modalPackChips) return;\n\n      while (modalPackChips.firstChild) modalPackChips.removeChild(modalPackChips.firstChild);\n\n      // Always include MERGED\n      var mergedBtn = document.createElement(\"button\");\n      mergedBtn.type = \"button\";\n      mergedBtn.className = \"packChip\" + (state.modal_hook_pack_mode === \"merged\" ? \" active\" : \"\");\n      mergedBtn.setAttribute(\"aria-label\", \"Select MERGED hooks (all imported packs)\");\n      mergedBtn.textContent = \"MERGED\";\n      mergedBtn.onclick = function () { setModalHookPack(\"merged\", \"\", true); };\n      modalPackChips.appendChild(mergedBtn);\n\n      // Add each imported file as a chip\n      for (var i = 0; i < state.imports.length; i++) (function (imp) {\n        var b = document.createElement(\"button\");\n        b.type = \"button\";\n        var isActive = (state.modal_hook_pack_mode === \"single\" && String(state.modal_hook_pack_id) === String(imp.id));\n        b.className = \"packChip\" + (isActive ? \" active\" : \"\");\n        b.setAttribute(\"aria-label\", \"Select hook pack \" + safeImportName(imp));\n        b.textContent = safeImportName(imp);\n        b.onclick = function () { setModalHookPack(\"single\", imp.id, true); };\n        modalPackChips.appendChild(b);\n      })(state.imports[i]);\n\n      // Active pill text\n      if (modalActivePackPill) {\n        if (state.modal_hook_pack_mode === \"merged\") {\n          modalActivePackPill.textContent = \"active: MERGED\";\n        } else {\n          var impA = findImportById(state.modal_hook_pack_id);\n          modalActivePackPill.textContent = \"active: \" + (impA ? safeImportName(impA) : \"MERGED\");\n          if (!impA) {\n            state.modal_hook_pack_mode = \"merged\";\n            state.modal_hook_pack_id = \"\";\n            modalActivePackPill.textContent = \"active: MERGED\";\n          }\n        }\n      }\n    }\n\n    // UPDATE #2: render continuity lock controls when applicable\n    function renderLockBarForPortal(portalKey) {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      var lock = getTabContinuity(tab);\n      var engaged = !!lock.engaged;\n\n      if (!lockBar) return;\n      if (String(portalKey) !== \"CONTINUITY_LOCK\") {\n        lockBar.style.display = \"none\";\n        return;\n      }\n\n      lockBar.style.display = \"flex\";\n      if (lockStatusPill) {\n        lockStatusPill.textContent = engaged ? \"ENGAGED\" : \"DISENGAGED\";\n        lockStatusPill.className = \"lockPill\" + (engaged ? \" engaged\" : \"\");\n      }\n      if (lockEngageBtn) lockEngageBtn.disabled = engaged;\n      if (lockDisengageBtn) lockDisengageBtn.disabled = !engaged;\n    }\n\n    function isPortalLockedByContinuity(portalKey) {\n      var tab = findTab(state.activeId);\n      if (!tab) return false;\n      if (!continuityIsEngaged(tab)) return false;\n      return (portalKey === \"GENRE\" || portalKey === \"BPM\" || portalKey === \"KEY_SIGNATURE\");\n    }\n\n    function openPortalModal(portalKey) {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      state.activePortalKey = portalKey;\n      state.modalPortalKey = portalKey;\n\n      // UPDATE #2: enforce lock before opening\n      applyContinuityLockToPortals(tab);\n\n      // UPDATE #3: ensure SHA256_ID exists before opening\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      var current = (tab.portals && tab.portals[portalKey]) ? String(tab.portals[portalKey]) : \"\";\n\n      modalTitle.textContent = \"Portal Editor — [\" + prettyPortalLabel(portalKey) + \"]\";\n\n      // UPDATE #2: locked portals are read-only\n      var locked = isPortalLockedByContinuity(portalKey);\n      if (locked) {\n        modalSub.textContent = \"Continuity lock is ENGAGED. This portal is locked (read-only). Disengage in CONTINUITY LOCK to edit.\";\n        modalText.setAttribute(\"readonly\",\"readonly\");\n      } else {\n        modalSub.textContent = \"Type your entry, or click a hook to assign into this portal.\";\n        modalText.removeAttribute(\"readonly\");\n      }\n\n      modalText.value = current;\n\n      lastFocusEl = document.activeElement;\n\n      modalBg.style.display = \"flex\";\n      modalBg.setAttribute(\"aria-hidden\", \"false\");\n\n      // UPDATE: render pack menu in Portal Editor\n      renderHookPackMenu();\n\n      // UPDATE #2: continuity lock controls\n      renderLockBarForPortal(portalKey);\n\n      renderHookList();\n\n      activePortalName.textContent = \"[\" + prettyPortalLabel(portalKey) + \"]\";\n      showToast(\"Portal selected: [\" + prettyPortalLabel(portalKey) + \"].\", \"PORTAL\");\n\n      setTimeout(function () { try { modalText.focus(); } catch (e) {} }, 0);\n    }\n\n    function closeModal() {\n      modalBg.style.display = \"none\";\n      modalBg.setAttribute(\"aria-hidden\", \"true\");\n      state.modalPortalKey = \"\";\n      try { if (lastFocusEl && lastFocusEl.focus) lastFocusEl.focus(); } catch (e) {}\n    }\n\n    modalCloseBtn.onclick = function () { closeModal(); };\n    modalBg.onclick = function (e) { e = e || window.event; if (e.target === modalBg) closeModal(); };\n\n    // Imports manager modal\n    function openImportsManager() {\n      lastFocusElImports = document.activeElement;\n      importsBg.style.display = \"flex\";\n      importsBg.setAttribute(\"aria-hidden\", \"false\");\n      renderImportsManager();\n      showToast(\"Imports Manager opened.\", \"IMPORTS\");\n      setTimeout(function(){ try { importsCloseBtn.focus(); } catch(e){} }, 0);\n    }\n    function closeImportsManager() {\n      importsBg.style.display = \"none\";\n      importsBg.setAttribute(\"aria-hidden\", \"true\");\n      try { if (lastFocusElImports && lastFocusElImports.focus) lastFocusElImports.focus(); } catch (e) {}\n    }\n    importsCloseBtn.onclick = function(){ closeImportsManager(); };\n    importsBg.onclick = function(e){ e = e || window.event; if (e.target === importsBg) closeImportsManager(); };\n\n    // Prompt Entries modal\n    function openPromptEntries() {\n      lastFocusElPromptEntries = document.activeElement;\n      promptEntriesBg.style.display = \"flex\";\n      promptEntriesBg.setAttribute(\"aria-hidden\", \"false\");\n      renderPromptEntries();\n      showToast(\"Prompt Entries module opened.\", \"PROMPT\");\n      setTimeout(function(){ try { promptEntriesCloseBtn.focus(); } catch(e){} }, 0);\n    }\n    function closePromptEntries() {\n      promptEntriesBg.style.display = \"none\";\n      promptEntriesBg.setAttribute(\"aria-hidden\", \"true\");\n      try { if (lastFocusElPromptEntries && lastFocusElPromptEntries.focus) lastFocusElPromptEntries.focus(); } catch (e) {}\n    }\n    promptEntriesCloseBtn.onclick = function(){ closePromptEntries(); };\n    promptEntriesBg.onclick = function(e){ e = e || window.event; if (e.target === promptEntriesBg) closePromptEntries(); };\n\n    // ESC closes whichever modal is on top\n    document.addEventListener(\"keydown\", function (e) {\n      var k = e.key || e.keyCode;\n      if (k === \"Escape\" || k === 27) {\n        if (promptEntriesBg.style.display === \"flex\") { if (e.preventDefault) e.preventDefault(); closePromptEntries(); return; }\n        if (importsBg.style.display === \"flex\") { if (e.preventDefault) e.preventDefault(); closeImportsManager(); return; }\n        if (modalBg.style.display === \"flex\") { if (e.preventDefault) e.preventDefault(); closeModal(); return; }\n      }\n    });\n\n    modalSaveBtn.onclick = function () {\n      if (!state.modalPortalKey) return;\n\n      // UPDATE #2: if locked, do not save edits\n      if (isPortalLockedByContinuity(state.modalPortalKey)) {\n        showToast(\"This portal is locked by Continuity Lock. Disengage to edit.\", \"LOCK\");\n        closeModal();\n        return;\n      }\n\n      updatePortalValue(state.modalPortalKey, modalText.value);\n      showToast(\"Saved portal [\" + prettyPortalLabel(state.modalPortalKey) + \"].\", \"PORTAL\");\n      closeModal();\n    };\n\n    modalAppendSelectedHookBtn.onclick = function () {\n      if (!state.modalPortalKey) return;\n\n      // UPDATE #2: if locked, block append\n      if (isPortalLockedByContinuity(state.modalPortalKey)) {\n        showToast(\"This portal is locked by Continuity Lock. Disengage to edit.\", \"LOCK\");\n        return;\n      }\n\n      if (!state.selectedHook) { showToast(\"No hook selected.\", \"HOOKS\"); return; }\n      var cur = modalText.value || \"\";\n      modalText.value = cur ? (cur + \"\\n\" + state.selectedHook) : state.selectedHook;\n      showToast(\"Hook appended to portal draft.\", \"HOOKS\");\n      try { modalText.focus(); } catch (e) {}\n    };\n\n    // UPDATE #2: Continuity lock modal buttons\n    if (lockAddBtn) lockAddBtn.onclick = function () {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      snapshotContinuityFromCurrent(tab);\n      setContinuityLockPortalFromMeta(tab);\n      renderLockBarForPortal(\"CONTINUITY_LOCK\");\n      renderContinuityStatusPill();\n      renderPortals();\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \"\";\n      showToast(\"Continuity lock ADDED + ENGAGED (GENRE+BPM+KEY).\", \"LOCK\");\n    };\n    if (lockEngageBtn) lockEngageBtn.onclick = function () {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      engageContinuity(tab); // UPDATE #4 now captures current portal inputs at engage time\n      setContinuityLockPortalFromMeta(tab);\n      renderLockBarForPortal(\"CONTINUITY_LOCK\");\n      renderContinuityStatusPill();\n      renderPortals();\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \"\";\n      showToast(\"Continuity lock ENGAGED.\", \"LOCK\");\n    };\n    if (lockDisengageBtn) lockDisengageBtn.onclick = function () {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      disengageContinuity(tab);\n      renderLockBarForPortal(\"CONTINUITY_LOCK\");\n      renderContinuityStatusPill();\n      renderPortals();\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \"\";\n      showToast(\"Continuity lock DISENGAGED.\", \"LOCK\");\n    };\n\n    // =========================\n    // Hooks import (bracketed strings)\n    // =========================\n    function parseHooksFromAnyJSON(obj) {\n      var hooks = [];\n      function addFromString(s) {\n        s = String(s || \"\");\n        var re = /\\[[^\\[\\]]+?\\]/g, m;\n        while ((m = re.exec(s)) !== null) hooks.push(m[0]);\n      }\n      function walk(v, depth) {\n        if (depth > 6) return;\n        if (v === null || v === undefined) return;\n        var t = Object.prototype.toString.call(v);\n        if (t === \"[object String]\") { addFromString(v); return; }\n        if (t === \"[object Array]\") { for (var i = 0; i < v.length; i++) walk(v[i], depth + 1); return; }\n        if (t === \"[object Object]\") { for (var k in v) if (Object.prototype.hasOwnProperty.call(v, k)) walk(v[k], depth + 1); return; }\n      }\n      walk(obj, 0);\n\n      var seen = {}, out = [];\n      for (var i = 0; i < hooks.length; i++) if (!seen[hooks[i]]) { seen[hooks[i]] = true; out.push(hooks[i]); }\n      return out;\n    }\n\n    // UPDATE: returns hooks for the selected pack in Portal Editor\n    function hooksForModalView() {\n      if (state.modal_hook_pack_mode === \"merged\") return (state.hooks && state.hooks.length) ? state.hooks : [];\n      var imp = findImportById(state.modal_hook_pack_id);\n      if (!imp || !imp.raw_text) return [];\n      // Cache parsed hooks on the import record (additive; safe)\n      if (imp._hooks_cache && Object.prototype.toString.call(imp._hooks_cache) === \"[object Array]\") return imp._hooks_cache;\n\n      var obj = null;\n      try { obj = JSON.parse(imp.raw_text); } catch (e) { obj = String(imp.raw_text || \"\"); }\n      var hooks = parseHooksFromAnyJSON(obj);\n      imp._hooks_cache = hooks;\n      return hooks;\n    }\n\n    function renderHookList() {\n      var listHooks = hooksForModalView();\n\n      if (!listHooks || !listHooks.length) {\n        // Friendly, accurate messaging based on modal selection\n        if (state.modal_hook_pack_mode === \"single\") {\n          var imp = findImportById(state.modal_hook_pack_id);\n          var label = imp ? safeImportName(imp) : \"import\";\n          hookList.innerHTML = '<div style=\"color:var(--muted);font-size:12px\">No bracketed hooks found in this pack: <b>' + esc(label) + '</b>. Switch to MERGED or import another file.</div>';\n        } else {\n          hookList.innerHTML = '<div style=\"color:var(--muted);font-size:12px\">No hook packs imported yet. Use “Import Hooks JSON”.</div>';\n        }\n        activeHookLabel.textContent = \"None\";\n        return;\n      }\n\n      var html = [];\n      for (var i = 0; i < listHooks.length; i++) {\n        var hook = listHooks[i];\n        html.push(\n          '<div class=\"hookItem\">'\n            + '<div class=\"hookText\">' + esc(hook) + '</div>'\n            + '<div class=\"hookBtns\">'\n              + '<button class=\"btn small\" type=\"button\" data-hook=\"' + esc(hook) + '\" data-action=\"select\">Select</button>'\n              + '<button class=\"btn small secondary\" type=\"button\" data-hook=\"' + esc(hook) + '\" data-action=\"append\">Append</button>'\n            + '</div>'\n          + '</div>'\n        );\n      }\n      hookList.innerHTML = html.join(\"\");\n      bindHookButtons();\n      activeHookLabel.textContent = state.selectedHook ? state.selectedHook : \"None\";\n    }\n\n    function bindHookButtons() {\n      var btns = hookList.querySelectorAll(\"button[data-action]\");\n      for (var i = 0; i < btns.length; i++) {\n        btns[i].onclick = function () {\n          var action = this.getAttribute(\"data-action\");\n          var hook = this.getAttribute(\"data-hook\") || \"\";\n          if (!hook) return;\n\n          if (action === \"select\") {\n            state.selectedHook = hook;\n            activeHookLabel.textContent = hook;\n            showToast(\"Hook selected.\", \"HOOKS\");\n          } else if (action === \"append\") {\n            state.selectedHook = hook;\n            activeHookLabel.textContent = hook;\n\n            if (modalBg.style.display === \"flex\" && state.modalPortalKey) {\n              // UPDATE #2: do not append into locked portals\n              if (isPortalLockedByContinuity(state.modalPortalKey)) {\n                showToast(\"This portal is locked by Continuity Lock. Disengage to edit.\", \"LOCK\");\n                return;\n              }\n              var cur = modalText.value || \"\";\n              modalText.value = cur ? (cur + \"\\n\" + hook) : hook;\n              showToast(\"Hook appended to portal draft.\", \"HOOKS\");\n              try { modalText.focus(); } catch (e) {}\n            } else {\n              showToast(\"Open a portal to append hooks.\", \"HOOKS\");\n            }\n          }\n        };\n      }\n    }\n\n    // =========================\n    // Imports Manager (multi-file)\n    // =========================\n    function getImportsStore() {\n      var raw = storageGet(\"mh8_music_imported_json_files_v1\", \"[]\");\n      try { var arr = JSON.parse(raw); return (Object.prototype.toString.call(arr) === \"[object Array]\") ? arr : []; }\n      catch (e) { return []; }\n    }\n    function setImportsStore(arr) { return storageSet(\"mh8_music_imported_json_files_v1\", JSON.stringify(arr)); }\n\n    function rebuildMergedHooksFromImports() {\n      var all = [], seen = {};\n      for (var i = 0; i < state.imports.length; i++) {\n        var imp = state.imports[i];\n        if (!imp || !imp.raw_text) continue;\n\n        // Clear modal cache on rebuild (additive, safe)\n        try { imp._hooks_cache = null; } catch (e) {}\n\n        var obj = null;\n        try { obj = JSON.parse(imp.raw_text); } catch (e) { obj = String(imp.raw_text || \"\"); }\n        var hooks = parseHooksFromAnyJSON(obj);\n        imp.hooks_count = hooks.length;\n\n        for (var j = 0; j < hooks.length; j++) {\n          var h = hooks[j];\n          if (!seen[h]) { seen[h] = true; all.push(h); }\n        }\n      }\n\n      state.hooks = all;\n      storageSet(\"mh8_music_imported_hooks\", JSON.stringify(all));\n      setImportsStore(state.imports);\n\n      // UPDATE: keep Portal Editor pack selection valid\n      if (state.modal_hook_pack_mode === \"single\" && state.modal_hook_pack_id) {\n        if (!findImportById(state.modal_hook_pack_id)) {\n          state.modal_hook_pack_mode = \"merged\";\n          state.modal_hook_pack_id = \"\";\n        }\n      }\n\n      if (modalBg.style.display === \"flex\") {\n        renderHookPackMenu();\n        renderHookList();\n      }\n      renderImportsManager();\n      return all.length;\n    }\n\n    function addImportFileRecord(filename, rawText) {\n      var rec = { id: uid(), name: String(filename || (\"import_\" + nowISO())), created_utc: nowISO(), raw_text: String(rawText || \"\"), hooks_count: 0 };\n      state.imports.push(rec);\n      var mergedCount = rebuildMergedHooksFromImports();\n      showToast(\"Hook pack added. Active merged hooks: \" + mergedCount + \".\", \"IMPORT\");\n    }\n\n    function deleteSingleImportById(id) {\n      var idx = -1;\n      for (var i = 0; i < state.imports.length; i++) if (String(state.imports[i].id) === String(id)) { idx = i; break; }\n      if (idx < 0) return;\n      var name = state.imports[idx].name;\n\n      state.imports.splice(idx, 1);\n\n      // UPDATE: if the deleted pack was active in Portal Editor, revert to MERGED\n      if (state.modal_hook_pack_mode === \"single\" && String(state.modal_hook_pack_id) === String(id)) {\n        state.modal_hook_pack_mode = \"merged\";\n        state.modal_hook_pack_id = \"\";\n      }\n\n      var mergedCount = rebuildMergedHooksFromImports();\n      showToast(\"Deleted import: \" + name + \". Merged hooks: \" + mergedCount + \".\", \"IMPORTS\");\n    }\n\n    function clearAllImports() {\n      state.imports = [];\n      state.hooks = [];\n      storageRemove(\"mh8_music_imported_json_files_v1\");\n      storageRemove(\"mh8_music_imported_hooks\");\n      state.selectedHook = \"\";\n\n      // UPDATE: reset Portal Editor pack selection\n      state.modal_hook_pack_mode = \"merged\";\n      state.modal_hook_pack_id = \"\";\n\n      renderHookPackMenu();\n      renderHookList();\n      renderImportsManager();\n      showToast(\"All hook packs cleared (local).\", \"IMPORTS\");\n    }\n\n    function exportAllImportsAsStringArray() {\n      var arr = [];\n      for (var i = 0; i < state.imports.length; i++) arr.push(String(state.imports[i].raw_text || \"\"));\n      var text = JSON.stringify(arr, null, 2);\n      var ok = downloadText(\"mh8-music-hookpacks-string-array.json\", text, \"application/json\");\n      showToast(ok ? \"All imports exported (string array).\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n\n    function renderImportsManager() {\n      var count = state.imports.length;\n      importsCountPill.textContent = String(count);\n      importsMergedHooksPill.textContent = String(state.hooks ? state.hooks.length : 0);\n      importsActiveModePill.textContent = \"MERGED\";\n\n      if (!count) {\n        importsList.innerHTML = '<div class=\"miniNote\">No imports yet. Use “Import Hooks JSON” to add one or more files.</div>';\n        return;\n      }\n\n      var html = [];\n      for (var i = 0; i < state.imports.length; i++) {\n        var imp = state.imports[i];\n        var name = imp.name || \"import\";\n        var ts = imp.created_utc || \"\";\n        var hc = imp.hooks_count || 0;\n        var bytes = (imp.raw_text && imp.raw_text.length) ? imp.raw_text.length : 0;\n\n        html.push(\n          '<div class=\"importRow\">'\n            + '<div class=\"importMeta\">'\n              + '<div class=\"importName\">' + esc(name) + '</div>'\n              + '<div class=\"importSub\">'\n                + 'hooks: <b>' + esc(hc) + '</b>'\n                + ' • chars: ' + esc(bytes)\n                + ' • ts: ' + esc(ts)\n              + '</div>'\n            + '</div>'\n            + '<div class=\"importBtns\">'\n              + '<button class=\"btn small\" type=\"button\" data-imp=\"' + esc(imp.id) + '\" data-impact=\"copy\">Copy JSON</button>'\n              + '<button class=\"btn small secondary\" type=\"button\" data-imp=\"' + esc(imp.id) + '\" data-impact=\"delete\">Delete</button>'\n            + '</div>'\n          + '</div>'\n        );\n      }\n\n      importsList.innerHTML = html.join(\"\");\n      bindImportsManagerButtons();\n    }\n\n    function bindImportsManagerButtons() {\n      var btns = importsList.querySelectorAll(\"button[data-impact]\");\n      for (var i = 0; i < btns.length; i++) {\n        btns[i].onclick = function () {\n          var act = this.getAttribute(\"data-impact\");\n          var id = this.getAttribute(\"data-imp\");\n          if (!id) return;\n\n          if (act === \"delete\") deleteSingleImportById(id);\n          else if (act === \"copy\") {\n            for (var j = 0; j < state.imports.length; j++) {\n              if (String(state.imports[j].id) === String(id)) {\n                copyAnyText(String(state.imports[j].raw_text || \"\"));\n                showToast(\"Imported JSON copied.\", \"COPY\");\n                break;\n              }\n            }\n          }\n        };\n      }\n    }\n\n    function handleImportedJSONText(text, filenameForManager) {\n      var obj = null;\n      try { obj = JSON.parse(text); } catch (e) { obj = String(text || \"\"); }\n\n      var hooks = parseHooksFromAnyJSON(obj);\n      if (!hooks.length) { showToast(\"No bracketed hooks found in import.\", \"IMPORT\"); return; }\n\n      addImportFileRecord(filenameForManager || \"hookpack.json\", String(text || \"\"));\n      if (modalBg.style.display === \"flex\") {\n        renderHookPackMenu();\n        renderHookList();\n      }\n    }\n\n    function importFromFile(file) {\n      try {\n        var reader = new FileReader();\n        reader.onload = function () { handleImportedJSONText(String(reader.result || \"\"), (file && file.name) ? file.name : \"hookpack.json\"); };\n        reader.onerror = function () { showToast(\"Import failed (file read).\", \"IMPORT\"); };\n        reader.readAsText(file);\n      } catch (e) { showToast(\"Import failed (unsupported).\", \"IMPORT\"); }\n    }\n\n    // =========================\n    // SHA-256 (crypto.subtle + fallback)\n    // =========================\n    function utf8Bytes(str) {\n      if (window.TextEncoder) return new TextEncoder().encode(str);\n      var utf8 = unescape(encodeURIComponent(str));\n      var arr = new Array(utf8.length);\n      for (var i = 0; i < utf8.length; i++) arr[i] = utf8.charCodeAt(i);\n      return arr;\n    }\n    function bytesToHex(bytes) {\n      var hex = \"\", i, b, h;\n      for (i = 0; i < bytes.length; i++) { b = bytes[i] & 255; h = b.toString(16); if (h.length < 2) h = \"0\" + h; hex += h; }\n      return hex;\n    }\n    function sha256Fallback(ascii) {\n      function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); }\n      var mathPow = Math.pow, maxWord = mathPow(2, 32), lengthProperty = \"length\";\n      var i, j, result = \"\", words = [], asciiBitLength = ascii[lengthProperty] * 8;\n      var hash = sha256Fallback.h = sha256Fallback.h || [];\n      var k = sha256Fallback.k = sha256Fallback.k || [];\n      var primeCounter = k[lengthProperty], isComposite = {};\n      for (var candidate = 2; primeCounter < 64; candidate++) {\n        if (!isComposite[candidate]) {\n          for (i = 0; i < 313; i += candidate) isComposite[i] = candidate;\n          hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;\n          k[primeCounter++] = (mathPow(candidate, 1/3) * maxWord) | 0;\n        }\n      }\n      ascii += \"\\x80\";\n      while (ascii[lengthProperty] % 64 - 56) ascii += \"\\x00\";\n      for (i = 0; i < ascii[lengthProperty]; i++) { j = ascii.charCodeAt(i); words[i >> 2] |= j << ((3 - i) % 4) * 8; }\n      words[words[lengthProperty]] = (asciiBitLength / maxWord) | 0;\n      words[words[lengthProperty]] = (asciiBitLength);\n      for (j = 0; j < words[lengthProperty];) {\n        var w = words.slice(j, j += 16), oldHash = hash.slice(0);\n        for (i = 0; i < 64; i++) {\n          var w15 = w[i - 15], w2 = w[i - 2];\n          var a = hash[0], e = hash[4];\n          var temp1 = hash[7]\n            + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25))\n            + ((e & hash[5]) ^ ((~e) & hash[6]))\n            + k[i]\n            + (w[i] = (i < 16) ? w[i] : (\n              w[i - 16]\n              + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3))\n              + w[i - 7]\n              + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))\n            ) | 0);\n          var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22))\n            + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));\n          hash = [(temp1 + temp2) | 0].concat(hash);\n          hash[4] = (hash[4] + temp1) | 0;\n          hash.pop();\n        }\n        for (i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;\n      }\n      for (i = 0; i < 8; i++) {\n        for (j = 3; j + 1; j--) {\n          var bb = (hash[i] >> (j * 8)) & 255;\n          result += ((bb < 16) ? \"0\" : \"\") + bb.toString(16);\n        }\n      }\n      return result;\n    }\n    function sha256Hex(message, cb) {\n      try {\n        if (window.crypto && window.crypto.subtle) {\n          var bytes = utf8Bytes(message);\n          var ab = bytes.buffer ? bytes.buffer : (new Uint8Array(bytes)).buffer;\n          window.crypto.subtle.digest(\"SHA-256\", ab).then(function (hashBuffer) {\n            cb(null, bytesToHex(new Uint8Array(hashBuffer)));\n          }).catch(function () {\n            cb(null, sha256Fallback(unescape(encodeURIComponent(message))));\n          });\n          return;\n        }\n      } catch (e) {}\n      cb(null, sha256Fallback(unescape(encodeURIComponent(message))));\n    }\n\n    // =========================\n    // Deterministic JSON (stable order)\n    // =========================\n    function stableStringify(value) {\n      var t = Object.prototype.toString.call(value);\n      if (t === \"[object Null]\" || t === \"[object Undefined]\") return \"null\";\n      if (t === \"[object Number]\" || t === \"[object Boolean]\") return JSON.stringify(value);\n      if (t === \"[object String]\") return JSON.stringify(String(value));\n      if (t === \"[object Array]\") {\n        var a = [];\n        for (var i = 0; i < value.length; i++) a.push(stableStringify(value[i]));\n        return \"[\" + a.join(\",\") + \"]\";\n      }\n      if (t === \"[object Object]\") {\n        var keys = [];\n        for (var k in value) if (Object.prototype.hasOwnProperty.call(value, k)) keys.push(k);\n        keys.sort();\n        var parts = [];\n        for (i = 0; i < keys.length; i++) parts.push(JSON.stringify(keys[i]) + \":\" + stableStringify(value[keys[i]]));\n        return \"{\" + parts.join(\",\") + \"}\";\n      }\n      return JSON.stringify(String(value));\n    }\n\n    // =========================\n    // Receipt + Prompt Entries\n    // =========================\n    function setReceiptText(text) {\n      state.receiptText = text || \"\";\n      receiptPre.textContent = state.receiptText ? state.receiptText : \"No receipt minted yet.\";\n      if (state.receiptText) storageSet(\"mh8_music_receipt\", state.receiptText);\n      else storageRemove(\"mh8_music_receipt\");\n    }\n\n    function setPromptEntriesText(text) {\n      state.promptEntriesText = text || \"\";\n      if (state.promptEntriesText) storageSet(\"mh8_music_prompt_entries\", state.promptEntriesText);\n      else storageRemove(\"mh8_music_prompt_entries\");\n      renderPromptEntries();\n    }\n\n    function renderPromptEntries() {\n      var t = state.promptEntriesText ? state.promptEntriesText : \"No prompt entries completed yet. Mint a receipt first.\";\n      promptEntriesPre.textContent = t;\n    }\n\n    // Layer 1: Lyrics (paste)\n    function buildLyricsSection(portals) {\n      var lyrics = String((portals && portals.LYRICS) ? portals.LYRICS : \"\").trim();\n      return lyrics;\n    }\n\n    // Layer 2: Style / tags / settings (paste)\n    function buildStyleSection(platform, portals) {\n      platform = String(platform || \"suno\");\n      var p = portals || {};\n\n      function line(lbl, v) {\n        v = String(v || \"\").trim();\n        if (!v) return null;\n        return \"• [\" + lbl + \"] \" + v;\n      }\n\n      var lines = [];\n      lines.push(\"• [PLATFORM] \" + (platform === \"suno\" ? \"SUNO\" : platform === \"udio\" ? \"UDIO\" : \"CUSTOM\"));\n\n      var l;\n      l = line(\"MODEL_BEHAVIOR_CONTROL\", p.MODEL_BEHAVIOR_CONTROL); if (l) lines.push(l);\n\n      l = line(\"GENRE\", p.GENRE); if (l) lines.push(l);\n      l = line(\"MOOD\", p.MOOD); if (l) lines.push(l);\n      l = line(\"ENERGY\", p.ENERGY); if (l) lines.push(l);\n      l = line(\"VOCAL_INTENSITY\", p.VOCAL_INTENSITY); if (l) lines.push(l);\n      l = line(\"ERA\", p.ERA_INFLUENCE); if (l) lines.push(l);\n      l = line(\"BPM\", p.BPM); if (l) lines.push(l);\n      l = line(\"KEY_SIGNATURE\", p.KEY_SIGNATURE); if (l) lines.push(l);\n\n      l = line(\"BACKING_VOX\", p.BACKING_VOX); if (l) lines.push(l);\n      l = line(\"DRUMS\", p.DRUMS); if (l) lines.push(l);\n      l = line(\"GUITAR\", p.GUITAR); if (l) lines.push(l);\n      l = line(\"BASS\", p.BASS); if (l) lines.push(l);\n      l = line(\"PIANO_KEYS\", p.PIANO_KEYS); if (l) lines.push(l);\n      l = line(\"SYNTH\", p.SYNTH); if (l) lines.push(l);\n      l = line(\"PERCUSSION\", p.PERCUSSION); if (l) lines.push(l);\n\n      l = line(\"DYNAMICS\", p.DYNAMICS); if (l) lines.push(l);\n      l = line(\"MIX\", p.MIX); if (l) lines.push(l);\n      l = line(\"MASTER\", p.MASTER); if (l) lines.push(l);\n\n      var lock = String(p.CONTINUITY_LOCK || \"\").trim();\n      if (lock) lines.push(\"• [CONTINUITY_LOCK] \" + lock.replace(/\\r?\\n/g, \" | \"));\n\n      // UPDATE #3: include SHA256 ID portal entry in Layer 2\n      l = line(\"SHA256_ID\", p.SHA256_ID); if (l) lines.push(l);\n\n      var xh = String(p.EXTRA_HOOKS || \"\").trim();\n      if (xh) {\n        lines.push(\"\");\n        lines.push(\"• [EXTRA_HOOKS] (sealed)\");\n        var xl = xh.split(/\\r?\\n/).map(function(s){ return s.trim(); }).filter(Boolean);\n        for (var i = 0; i < xl.length; i++) lines.push(\"  \" + xl[i]);\n      }\n\n      var notes = String(p.NOTES || \"\").trim();\n      if (notes) {\n        lines.push(\"\");\n        lines.push(\"• [NOTES]\");\n        var nl = notes.split(/\\r?\\n/);\n        for (i = 0; i < nl.length; i++) lines.push(\"  \" + String(nl[i]));\n      }\n\n      return lines.join(\"\\n\").trim();\n    }\n\n    // UPDATE #5: include minted SHA256 as LAST bracketed entry in Prompt Entries Completed\n    function buildPromptEntriesCompleted(layer1, layer2, mintedSha256Hex) {\n      var out = [];\n      out.push(\"[PROMPT ENTRIES COMPLETED — COPY/PASTE]\");\n      out.push(\"INSTRUCTIONS: Copy/paste the two blocks below into your AI music platform. Layer 3 is NOT included here.\");\n      out.push(\"\");\n      out.push(\"[LAYER 1 — LYRICS (PASTE THIS)]\");\n      out.push(\"Paste into the platform’s LYRICS field.\");\n      out.push(\"\");\n      out.push(layer1 || \"\");\n      out.push(\"\");\n      out.push(\"[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\");\n      out.push(\"Paste into the platform’s STYLE / PROMPT / TAGS field.\");\n      out.push(\"\");\n      out.push(layer2 || \"\");\n\n      // REQUIRED: last entry\n      out.push(\"\");\n      out.push(\"[SHA 256 ID + \" + String(mintedSha256Hex || \"\").trim() + \"]\");\n\n      return out.join(\"\\n\").replace(/\\n{3,}/g, \"\\n\\n\");\n    }\n\n    function mintReceipt() {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      // UPDATE #2: enforce continuity lock before minting (ensures receipts reflect locked values)\n      applyContinuityLockToPortals(tab);\n\n      // UPDATE #3: ensure SHA256_ID exists before minting\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      var platform = String(state.platform);\n      var ts = nowISO();\n      var brand = \"ACBEATZ.COM\";\n\n      var portals = tab.portals || makeEmptyPortals();\n      var layer1 = buildLyricsSection(portals);\n      var layer2 = buildStyleSection(platform, portals);\n\n      var corePayload = {\n        mh8_system: \"MH8-AI-MUSIC-PROOF-OF-CREATION-VAULT\",\n        receipt_type: \"MH8-PROMPT-TRIPLE-LAYER\",\n        receipt_version: \"2.0.0\",\n        created_utc: ts,\n        brand: brand,\n        platform: platform,\n        prompt_name: tab.name,\n        layers: {\n          layer_1_lyrics: layer1,\n          layer_2_style: layer2\n        },\n        portals: portals,\n\n        // UPDATE #2: continuity meta is included in receipt payload (additive)\n        continuity_lock: getTabContinuity(tab),\n\n        imported_hooks_count: state.hooks ? state.hooks.length : 0\n      };\n\n      var canonical = stableStringify(corePayload);\n      var hashInput = \"MH8-Acbeatz.com|\" + canonical;\n\n      mintBtn.disabled = true;\n      mintBtn.textContent = \"Minting...\";\n\n      sha256Hex(hashInput, function (err, hash) {\n        mintBtn.disabled = false;\n        mintBtn.textContent = \"Mint Proof Receipt\";\n        if (err) { showToast(\"Mint failed. (Hash error)\", \"MINT\"); return; }\n\n        var humanPretty =\n          \"MH8-Acbeatz.com — MH8 AI Music Proof-of-Creation Vault\\n\" +\n          \"Receipt: Prompt (triple-layer)\\n\" +\n          \"Logic: Lyrics + Style + Hooks cryptographically sealed\\n\" +\n          \"Timestamp: \" + ts + \"\\n\" +\n          \"SHA256: \" + hash + \"\\n\" +\n          \"Brand: \" + brand + \"\\n\" +\n          \"Crypto receipt: ✅ Complete (local SHA-256)\\n\" +\n          \"Integrity rule: NON-COPIABLE WHEN HASH-CHAIN BROKEN\\n\" +\n          \"© 2026 ACBEATZ.COM — All rights reserved.\";\n\n        var receiptDisplay = [];\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"LAYER 1 — LYRICS (PASTE THIS)\");\n        receiptDisplay.push(\"INSTRUCTIONS: Paste ONLY this layer into the AI music platform’s LYRICS field.\");\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(layer1 || \"\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)\");\n        receiptDisplay.push(\"INSTRUCTIONS: Paste ONLY this layer into the platform’s STYLE / PROMPT / TAGS field.\");\n        receiptDisplay.push(\"NOTE: This includes continuity lock + extra hooks + SHA256 ID if provided.\");\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(layer2 || \"\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(\"=========================================\");\n        receiptDisplay.push(\"LAYER 3 — HASHED RECEIPT (DO NOT PASTE)\");\n        receiptDisplay.push(\"INSTRUCTIONS: Do NOT paste this into any AI music platform.\");\n        receiptDisplay.push(\"Keep it as proof-of-creation for audit/dispute/reference.\");\n        receiptDisplay.push(\"=========================================\");\n        receiptDisplay.push(\"\");\n\n        var machineHard = {\n          mh8_system: \"ACBEATZ.COM\",\n          brand: brand,\n          created_utc: ts,\n          integrity_rule: \"NON-COPIABLE WHEN HASH-CHAIN BROKEN\",\n          core_payload: corePayload,\n          canonical_payload: canonical,\n          hash_input: hashInput,\n          sha256_hex: hash,\n          cryptographic_receipt_status: \"VERIFIED_LOCAL\"\n        };\n\n        var finalObj = {\n          human_pretty: humanPretty,\n          machine_hard: machineHard,\n          created_utc: ts,\n          sha256: hash,\n          payload: {\n            platform: platform,\n            prompt_name: tab.name,\n            layer_1_lyrics: layer1,\n            layer_2_style: layer2,\n            portals: portals,\n\n            // UPDATE #2: explicit continuity snapshot in payload mirror (additive)\n            continuity_lock: getTabContinuity(tab)\n          },\n          receipt_text: receiptDisplay.join(\"\\n\")\n        };\n\n        var fullReceipt = finalObj.receipt_text + \"\\n\\n\" + JSON.stringify(finalObj, null, 2);\n        finalObj.full_receipt = fullReceipt;\n\n        setReceiptText(fullReceipt);\n\n        // UPDATE #5: pass minted SHA256 into Prompt Entries Completed and append as LAST line\n        var pe = buildPromptEntriesCompleted(layer1, layer2, hash);\n        setPromptEntriesText(pe);\n\n        var vault = getVault();\n        vault.push(finalObj);\n        var ok = setVault(vault);\n        showToast(ok ? \"Receipt minted + logged locally.\" : \"Receipt minted. Log storage blocked (device policy).\", \"MINT\");\n        if (state.showLog) renderVault();\n      });\n    }\n\n    // =========================\n    // Copy helpers\n    // =========================\n    function fallbackCopyText(text) {\n      try {\n        legacyCopyArea.value = text;\n        legacyCopyArea.style.display = \"block\";\n        legacyCopyArea.select();\n        legacyCopyArea.setSelectionRange(0, legacyCopyArea.value.length);\n        var ok = document.execCommand(\"copy\");\n        legacyCopyArea.style.display = \"none\";\n        return ok;\n      } catch (e) {\n        try { legacyCopyArea.style.display = \"none\"; } catch (e2) {}\n        return false;\n      }\n    }\n    function copyAnyText(text) {\n      if (!text) return;\n      try {\n        if (navigator.clipboard && window.isSecureContext) {\n          navigator.clipboard.writeText(text).then(function () {\n            showToast(\"Copied to clipboard.\", \"COPY\");\n          }).catch(function () {\n            var ok = fallbackCopyText(text);\n            showToast(ok ? \"Copied (legacy mode).\" : \"Copy failed (browser restriction).\", \"COPY\");\n          });\n          return;\n        }\n      } catch (e) {}\n      var ok2 = fallbackCopyText(text);\n      showToast(ok2 ? \"Copied (legacy mode).\" : \"Copy failed (browser restriction).\", \"COPY\");\n    }\n\n    function copyReceipt() {\n      if (!state.receiptText) { showToast(\"Nothing to copy. Mint a receipt first.\", \"COPY\"); return; }\n      copyAnyText(state.receiptText);\n    }\n\n    // =========================\n    // Export\n    // =========================\n    function downloadText(filename, text, mime) {\n      mime = mime || \"application/octet-stream\";\n      try {\n        var blob = new Blob([text], { type: mime });\n        var a = document.createElement(\"a\");\n        a.href = URL.createObjectURL(blob);\n        a.download = filename;\n        document.body.appendChild(a);\n        a.click();\n        setTimeout(function () {\n          URL.revokeObjectURL(a.href);\n          document.body.removeChild(a);\n        }, 0);\n        return true;\n      } catch (e) {\n        try {\n          var a2 = document.createElement(\"a\");\n          a2.href = \"data:\" + mime + \";charset=utf-8,\" + encodeURIComponent(text);\n          a2.download = filename;\n          document.body.appendChild(a2);\n          a2.click();\n          document.body.removeChild(a2);\n          return true;\n        } catch (e2) { return false; }\n      }\n    }\n\n    function exportReceipt() {\n      if (!state.receiptText) { showToast(\"Nothing to export. Mint a receipt first.\", \"EXPORT\"); return; }\n      var ok = downloadText(\"mh8-music-receipt.txt\", state.receiptText, \"text/plain\");\n      showToast(ok ? \"Receipt exported.\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n\n    function exportVault() {\n      var vault = getVault();\n      var text = vault.length ? JSON.stringify(vault, null, 2) : \"[]\";\n      var ok = downloadText(\"mh8-music-vault.json\", text, \"application/json\");\n      showToast(ok ? \"Vault exported as JSON.\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n\n    function copyPromptEntries() {\n      if (!state.promptEntriesText) { showToast(\"Nothing to copy. Mint a receipt first.\", \"COPY\"); return; }\n      copyAnyText(state.promptEntriesText);\n    }\n    function exportPromptEntries() {\n      if (!state.promptEntriesText) { showToast(\"Nothing to export. Mint a receipt first.\", \"EXPORT\"); return; }\n      var ok = downloadText(\"mh8-music-prompt-entries-completed.txt\", state.promptEntriesText, \"text/plain\");\n      showToast(ok ? \"Prompt entries exported.\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n    function clearPromptEntries() {\n      setPromptEntriesText(\"\");\n      showToast(\"Prompt entries cleared.\", \"PROMPT\");\n    }\n\n    // =========================\n    // Log toggle\n    // =========================\n    function setLogVisible(visible) {\n      state.showLog = !!visible;\n      if (state.showLog) {\n        logCard.className = \"card logCompact\";\n        logBtn.setAttribute(\"aria-expanded\", \"true\");\n        renderVault();\n        showToast(\"Log opened (compact + scroll).\", \"LOG\");\n      } else {\n        logCard.className = \"card hidden logCompact\";\n        logBtn.setAttribute(\"aria-expanded\", \"false\");\n        showToast(\"Log closed.\", \"LOG\");\n      }\n      storageSet(\"mh8_music_show_log\", state.showLog ? \"1\" : \"0\");\n    }\n\n    // =========================\n    // Clear editor\n    // =========================\n    function clearEditor() {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      tab.portals = makeEmptyPortals();\n\n      // UPDATE #2: clear lock meta as well (still additive behavior; matches \"reset\")\n      tab.continuity_lock = { engaged:false, created_utc:\"\", genre:\"\", bpm:\"\", key_signature:\"\" };\n      setContinuityLockPortalFromMeta(tab);\n\n      // UPDATE #3: ensure SHA256_ID reset present\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      persistTabs();\n      renderPortals();\n      showToast(\"Editor cleared (all music portals reset).\", \"CLEAR\");\n    }\n\n    // =========================\n    // Receipt toggle\n    // =========================\n    function setReceiptOpen(open) {\n      state.receiptOpen = !!open;\n      storageSet(\"mh8_music_receipt_open\", state.receiptOpen ? \"1\" : \"0\");\n      receiptToggleBtn.setAttribute(\"aria-expanded\", state.receiptOpen ? \"true\" : \"false\");\n      receiptChev.textContent = state.receiptOpen ? \"▾\" : \"▸\";\n      receiptBody.className = state.receiptOpen ? \"cardBody\" : \"cardBody hidden\";\n    }\n    function toggleReceiptOpen() {\n      setReceiptOpen(!state.receiptOpen);\n      showToast(state.receiptOpen ? \"Receipt opened.\" : \"Receipt closed.\", \"RECEIPT\");\n    }\n\n    // =========================\n    // Load hooks\n    // =========================\n    function loadHooks() {\n      var rawImports = storageGet(\"mh8_music_imported_json_files_v1\", \"\");\n      if (rawImports) {\n        try {\n          var arr = JSON.parse(rawImports);\n          if (Object.prototype.toString.call(arr) === \"[object Array]\") {\n            state.imports = arr;\n            rebuildMergedHooksFromImports();\n            return;\n          }\n        } catch (e) {}\n      }\n\n      var raw = storageGet(\"mh8_music_imported_hooks\", \"\");\n      if (!raw) return;\n      try {\n        var arr2 = JSON.parse(raw);\n        if (Object.prototype.toString.call(arr2) === \"[object Array]\") state.hooks = arr2;\n      } catch (e) {}\n    }\n\n    // =========================\n    // Init\n    // =========================\n    function init() {\n      var savedPlatform = storageGet(\"mh8_music_platform\", \"suno\");\n      if (savedPlatform !== \"suno\" && savedPlatform !== \"udio\" && savedPlatform !== \"other\") savedPlatform = \"suno\";\n      state.platform = savedPlatform;\n      platformSelect.value = state.platform;\n\n      var savedTheme = storageGet(\"mh8_music_theme\", \"dark\");\n      if (savedTheme !== \"dark\" && savedTheme !== \"light\") savedTheme = \"dark\";\n      applyTheme(savedTheme);\n\n      var savedMode = storageGet(\"mh8_music_portal_mode\", \"quick\");\n      if (savedMode !== \"quick\" && savedMode !== \"semi\" && savedMode !== \"adv\") savedMode = \"quick\";\n      state.portal_mode = savedMode;\n\n      var ro = storageGet(\"mh8_music_receipt_open\", \"1\") === \"1\";\n      setReceiptOpen(ro);\n\n      loadTabs();\n      renderTabs();\n\n      loadHooks();\n\n      // UPDATE: initialize Portal Editor pack selector to MERGED\n      state.modal_hook_pack_mode = \"merged\";\n      state.modal_hook_pack_id = \"\";\n      renderHookPackMenu();\n\n      state.activePortalKey = \"MODEL_BEHAVIOR_CONTROL\";\n      renderModeButtons();\n      normalizeActivePortalForMode();\n\n      // UPDATE #2/#3: ensure continuity + SHA256_ID exist and portals are in sync on startup\n      var tab = findTab(state.activeId);\n      if (tab) {\n        getTabContinuity(tab);\n        setContinuityLockPortalFromMeta(tab);\n        if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n        applyContinuityLockToPortals(tab);\n      }\n\n      renderPortals();\n\n      var savedReceipt = storageGet(\"mh8_music_receipt\", \"\");\n      if (savedReceipt) setReceiptText(savedReceipt);\n\n      var savedPE = storageGet(\"mh8_music_prompt_entries\", \"\");\n      if (savedPE) setPromptEntriesText(savedPE);\n      else renderPromptEntries();\n\n      var show = storageGet(\"mh8_music_show_log\", \"0\") === \"1\";\n      setLogVisible(show);\n\n      showToast(\"Ready. Build lyrics + style, stack hooks, mint proof receipts.\", \"MH8\");\n    }\n\n    // =========================\n    // Events\n    // =========================\n    platformSelect.onchange = function () {\n      state.platform = platformSelect.value;\n      storageSet(\"mh8_music_platform\", state.platform);\n      showToast(\"Platform set to \" + (state.platform === \"suno\" ? \"Suno\" : state.platform === \"udio\" ? \"Udio\" : \"Custom\") + \".\", \"PLATFORM\");\n    };\n\n    themeBtn.onclick = function () { toggleTheme(); };\n    logBtn.onclick = function () { setLogVisible(!state.showLog); };\n    addTabBtn.onclick = function () { addTab(); };\n\n    importBtn.onclick = function () { try { importFile.click(); } catch (e) { showToast(\"Import blocked by browser.\", \"IMPORT\"); } };\n    importMgrBtn.onclick = function () { openImportsManager(); };\n\n    importFile.onchange = function () {\n      var files = importFile.files;\n      if (!files || !files.length) return;\n      importFromFile(files[0]);\n      try { importFile.value = \"\"; } catch (e) {}\n    };\n\n    importsMergeBtn.onclick = function(){ var merged = rebuildMergedHooksFromImports(); showToast(\"Merged hooks rebuilt: \" + merged + \".\", \"IMPORTS\"); };\n    importsExportAllBtn.onclick = function(){ exportAllImportsAsStringArray(); };\n    importsClearAllBtn.onclick = function(){ clearAllImports(); };\n\n    modeQuickBtn.onclick = function(){ setPortalMode(\"quick\", true); };\n    modeSemiBtn.onclick = function(){ setPortalMode(\"semi\", true); };\n    modeAdvBtn.onclick = function(){ setPortalMode(\"adv\", true); };\n\n    portalPrevBtn.onclick = function(){ goPrevPortal(); };\n    portalNextBtn.onclick = function(){ goNextPortal(); };\n    portalJump.onchange = function(){ var idx = Number(portalJump.value); setActivePortalByIndex(idx, true); };\n\n    // Keyboard arrows for portal flow (when not typing / not in modal)\n    document.addEventListener(\"keydown\", function(e){\n      e = e || window.event;\n      var k = e.key || e.keyCode;\n\n      var tag = (document.activeElement && document.activeElement.tagName) ? String(document.activeElement.tagName).toLowerCase() : \"\";\n      var isTyping = (tag === \"textarea\" || tag === \"input\" || tag === \"select\");\n\n      if (modalBg.style.display === \"flex\") return;\n      if (importsBg.style.display === \"flex\") return;\n      if (promptEntriesBg.style.display === \"flex\") return;\n      if (isTyping) return;\n\n      if (k === \"ArrowLeft\" || k === 37) { prevent(e); goPrevPortal(); }\n      else if (k === \"ArrowRight\" || k === 39) { prevent(e); goNextPortal(); }\n      else if (k === \"Enter\" || k === 13) { prevent(e); if (state.activePortalKey) openPortalModal(state.activePortalKey); }\n    });\n\n    mintBtn.onclick = function () { mintReceipt(); };\n    copyBtn.onclick = function () { copyReceipt(); };\n    exportBtn.onclick = function () { exportReceipt(); };\n\n    clearReceiptBtn.onclick = function () { setReceiptText(\"\"); showToast(\"Receipt cleared (screen only).\", \"RECEIPT\"); };\n    clearEditorBtn.onclick = function () { clearEditor(); };\n\n    refreshLogBtn.onclick = function () { renderVault(); showToast(\"Log refreshed.\", \"LOG\"); };\n    exportVaultBtn.onclick = function () { exportVault(); };\n    clearVaultBtn.onclick = function () {\n      var ok = storageRemove(\"mh8_music_vault\");\n      renderVault();\n      showToast(ok ? \"Local vault cleared.\" : \"Vault clear blocked by device policy.\", \"LOG\");\n    };\n\n    receiptToggleBtn.onclick = function () { toggleReceiptOpen(); };\n\n    promptEntriesBtn.onclick = function (e) { if (e && e.stopPropagation) e.stopPropagation(); openPromptEntries(); };\n    promptEntriesCopyBtn.onclick = function(){ copyPromptEntries(); };\n    promptEntriesExportBtn.onclick = function(){ exportPromptEntries(); };\n    promptEntriesClearBtn.onclick = function(){ clearPromptEntries(); };\n\n    // Boot\n    init();\n\n  })();\n  </script>\n</body>\n</html>\nTimestamp: 2026-02-17T00:19:38.924Z\nSHA256: 3c3e31eb12b36f7ebc11da31a314f6725cb344ad92267a757dc870088f22aa06\nBrand: ACBEATZ.COM\nCrypto receipt: ✅ Complete (local SHA-256)\nCourt / audit / dispute: ✅ Strong baseline (pair with artifact)\nIntegrity rule: NON-COPIABLE WHEN HASH-CHAIN BROKEN\n© 2026 ACBEATZ.COM — All rights reserved.",
  "machine_hard": {
    "mh8_system": "ACBEATZ.COM",
    "brand": "ACBEATZ.COM",
    "created_utc": "2026-02-17T00:19:38.924Z",
    "integrity_rule": "NON-COPIABLE WHEN HASH-CHAIN BROKEN",
    "core_payload": {
      "mh8_system": "MH8-PROTOCOL-HUB",
      "receipt_type": "MH8-PROTOCOL-HUB-CORE-MINT",
      "receipt_version": "PROTOCOL_HUB_UI_V13",
      "created_utc": "2026-02-17T00:19:38.924Z",
      "artifact": {
        "core_entry": "[MH8-Music Prompt Pro User Interface sha256 Sealed Code]\n[https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\nhttps://github.com/acbeatz\nhttps://acbeatz.com/n-eyes\nhttps://orcid.org/0009-0003-3846-9082]\n\n<!DOCTYPE html>  \n<html lang=\"en\">\n<head>\n  <!--\n    MH8 AI Music Proof-of-Creation Vault (Vanilla HTML5 SPA)\n    Version: 2.0.0\n    © 2026 ACBEATZ.COM\n    Commercial SaaS Ready | Legacy + Mobile Hardened\n\n    Shell: PROMPT UI FORMAT (canonical)\n    Converted from: MH8 AI Music Proof-of-Creation UI (style + lyrics + hooks + triple-layer receipt)\n\n    Core behavior preserved from PROMPT UI FORMAT:\n    - Single-file SPA, no dependencies\n    - Topbar: Platform select, Import Hooks JSON + Imports Manager, theme toggle, log toggle\n    - Prompt tabs w/ per-tab delete\n    - Entry Portals: tiered carousel (Quick/Semi/Advanced) with Prev/Next/dots/jump + keyboard arrows + modal editor\n    - Hooks import: bracketed string extraction; select/append; manual typing supported\n    - Imports Manager: list/merge/rebuild/delete/clear/export string-array of all imports\n    - Three-layer receipt minting + collapsible receipt header toggle + ARIA expanded\n    - PROMPT ENTRIES COMPLETED module: shows Layer 1+2 only with Copy/Clear/Export\n    - Local vault log: per-entry copy + export/clear\n    - Clipboard API + legacy fallback, toast + ARIA live, SHA-256 via crypto.subtle + fallback\n    - Inline SVG \"8\" favicon\n\n    Conversion changes for AI Music:\n    - Portal schema renamed for music prompts\n    - Receipt layers redefined:\n      Layer 1 = LYRICS (PASTE)\n      Layer 2 = STYLE / TAGS / SETTINGS (PASTE)\n      Layer 3 = HASHED RECEIPT (DO NOT PASTE)\n    - All headers/titles/labels updated to MH8 AI Music naming\n\n    UPDATE (Additive only):\n    - Portal Editor module sub-header menu listing ALL imported JSON hook packs (clickable)\n    - Allows selecting a specific pack OR MERGED (all) directly inside Portal Editor\n    - Hook list in Portal Editor filters to the selected pack; Append works exactly the same\n\n    UPDATE #1 (Additive only):\n    - Retitle UI header/title to: \"MH8-Music-Prompt-Pro\" (Ai Music-Creators-Console)\n    - All prior logic/features/functions remain verbatim.\n\n    UPDATE #2 (Additive only) — CONTINUITY LOCK FIX:\n    - CONTINUITY LOCK portal now supports: DISENGAGED / ENGAGED + ADD (snapshot) behavior\n    - When ENGAGED: locks GENRE + BPM + KEY_SIGNATURE (read-only in modal + updates blocked)\n    - Continuity lock content is included in Layer 2 + full receipt payload (portals + layers)\n    - Lock snapshot is deterministic and stored per-tab (does not affect other prompts)\n\n    UPDATE #3 (Additive only) — SHA256 ID PORTAL:\n    - Add a final portal to the END of each portals menu section (Quick/Semi/Advanced) titled \"SHA256 ID\"\n    - This portal holds the user’s SHA256 tracking # (production tracking ID)\n    - SHA256 ID portal entry is included in Layer 2 + full receipt payload\n    - Minted SHA256 (Layer 3 hash) remains present after minting as proof\n    - END OF UPDATES\n\n    UPDATE #4 (Additive only) — CONTINUITY LOCK RECOGNIZES USER PORTAL INPUTS:\n    - When ENGAGE is pressed (or when continuity is engaged programmatically), the lock now captures the CURRENT\n      portal values for GENRE, BPM, KEY_SIGNATURE and stores them into the continuity lock snapshot before enforcing.\n    - This ensures receipts show non-blank values like:\n      [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: X | BPM: Y | KEY_SIGNATURE: Z\n    - All other behavior remains unchanged.\n\n    UPDATE #5 (Additive only) — PROMPT ENTRIES COMPLETED MUST INCLUDE MINTED SHA256:\n    - When user clicks \"Mint Proof Receipt\", the minted SHA256 hash for that receipt is included in the\n      PROMPT ENTRIES COMPLETED output as the LAST entry:\n      [SHA 256 ID + hex64]\n    - Every minted receipt updates PROMPT ENTRIES COMPLETED with its unique SHA256 appended as final line.\n  -->\n  <meta charset=\"UTF-8\" />\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\" />\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n  <title>MH8-Music-Prompt-Pro (Ai Music-Creators-Console)</title>\n\n  <!-- SVG \"8\" favicon -->\n  <link rel=\"icon\" type=\"image/svg+xml\"\n    href='data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\"><rect width=\"64\" height=\"64\" rx=\"14\" fill=\"%2306070b\"/><path d=\"M32 12c-7 0-12 4.6-12 11 0 4.1 2.2 7.4 5.7 9.2C21.4 34 18 37.8 18 43c0 6.8 6 11 14 11s14-4.2 14-11c0-5.2-3.4-9-7.7-10.8C41.8 30.4 44 27.1 44 23c0-6.4-5-11-12-11zm0 6c3.7 0 6 2 6 5 0 3.1-2.5 5-6 5s-6-1.9-6-5c0-3 2.3-5 6-5zm0 16c4.7 0 8 2.7 8 6.5S36.7 47 32 47s-8-2.7-8-6.5S27.3 34 32 34z\" fill=\"%234fd1c5\"/></svg>' />\n\n  <style>\n    :root{\n      --bg:#06070b;--panel:#0d1224;--panel2:#0b1020;--text:#e9ecf5;--muted:#a7b0c3;\n      --line:rgba(255,255,255,.10);--accent:#4fd1c5;--accent2:#7c3aed;--danger:#f56565;\n      --shadow:0 12px 40px rgba(0,0,0,.35);\n      --radius:14px;\n      --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n      --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \"Apple Color Emoji\",\"Segoe UI Emoji\";\n      --orange:#f59e0b;\n    }\n    html[data-theme=\"light\"]{\n      --bg:#f6f7fb;--panel:#ffffff;--panel2:#ffffff;--text:#0b1220;--muted:#4b5565;\n      --line:rgba(15,23,42,.12);--shadow:0 16px 50px rgba(2,6,23,.10);\n    }\n    *{box-sizing:border-box}\n    body{margin:0;font-family:var(--sans);background:var(--bg);color:var(--text);min-height:100vh;}\n    .wrap{max-width:1200px;margin:0 auto;padding:14px}\n    .topbar{\n      display:flex;align-items:center;justify-content:space-between;gap:12px;\n      padding:12px 12px;border:1px solid var(--line);border-radius:var(--radius);\n      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));\n      box-shadow:var(--shadow);\n    }\n    html[data-theme=\"light\"] .topbar{background:linear-gradient(180deg, rgba(2,6,23,.03), rgba(2,6,23,.01))}\n    .brand{display:flex;flex-direction:column;gap:2px}\n    .brand h1{margin:0;font-size:16px;letter-spacing:.2px}\n    .brand .sub{font-size:12px;color:var(--muted)}\n    .controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end}\n    .select, .btn{\n      font-size:13px;border-radius:12px;border:1px solid var(--line);\n      background:rgba(255,255,255,.03);color:var(--text);\n      padding:9px 10px;outline:none;\n    }\n    html[data-theme=\"light\"] .select, html[data-theme=\"light\"] .btn{background:rgba(2,6,23,.03)}\n    .btn{cursor:pointer;user-select:none}\n    .btn:focus{box-shadow:0 0 0 3px rgba(79,209,197,.22)}\n    .btn.primary{border-color:rgba(79,209,197,.45); background:rgba(79,209,197,.10)}\n    .btn.secondary{border-color:rgba(124,58,237,.35); background:rgba(124,58,237,.10)}\n    .btn.danger{border-color:rgba(245,101,101,.35); background:rgba(245,101,101,.10)}\n    .btn.small{padding:7px 9px;border-radius:10px;font-size:12px}\n    .btn.icon{padding:7px 9px;border-radius:10px;font-size:12px;display:inline-flex;gap:6px;align-items:center}\n    .btn[disabled]{opacity:.55;cursor:not-allowed}\n\n    .tabsBar{margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;}\n    .tablist{\n      display:flex;gap:8px;align-items:center;flex-wrap:wrap;\n      padding:10px;border:1px solid var(--line);border-radius:var(--radius);\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .tablist{background:rgba(2,6,23,.02)}\n    .tabWrap{display:flex;align-items:center;gap:6px}\n    .tab{\n      border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text);\n      padding:8px 10px;border-radius:12px;cursor:pointer;font-size:13px;\n    }\n    html[data-theme=\"light\"] .tab{background:rgba(2,6,23,.03)}\n    .tab[aria-selected=\"true\"]{border-color:rgba(79,209,197,.55);background:rgba(79,209,197,.12);}\n    .tab:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.22)}\n    .tabDel{\n      border:1px solid var(--line);background:rgba(255,255,255,.02);color:var(--muted);\n      padding:8px 9px;border-radius:12px;cursor:pointer;font-size:12px;\n    }\n    html[data-theme=\"light\"] .tabDel{background:rgba(2,6,23,.02)}\n    .tabDel:hover{color:var(--text);border-color:rgba(245,101,101,.35);background:rgba(245,101,101,.10)}\n\n    .grid{margin-top:12px;display:grid;grid-template-columns:1fr;gap:12px;}\n    .footerZone{margin-top:12px;}\n    .footerZone .card{width:100%;}\n\n    .card{\n      border:1px solid var(--line);border-radius:var(--radius);\n      background:rgba(255,255,255,.02);box-shadow:var(--shadow);overflow:hidden;\n    }\n    html[data-theme=\"light\"] .card{background:rgba(2,6,23,.02)}\n    .cardHeader{\n      display:flex;align-items:center;justify-content:space-between;gap:10px;\n      padding:12px 12px;border-bottom:1px solid var(--line);\n    }\n    .cardHeader h2{margin:0;font-size:13px;letter-spacing:.2px}\n    .cardHeader .hint{color:var(--muted);font-size:12px}\n    .cardBody{padding:12px}\n    .actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}\n    .headerOrange{color:var(--orange);}\n\n    .copyrightFooter{\n      margin-top:10px;\n      padding:10px 12px;\n      border:1px solid var(--line);\n      border-radius:var(--radius);\n      background:rgba(255,255,255,.02);\n      color:var(--muted);\n      font-size:12px;\n      text-align:center;\n    }\n    html[data-theme=\"light\"] .copyrightFooter{background:rgba(2,6,23,.02)}\n\n    .portal{\n      border:1px solid var(--line);\n      border-radius:14px;\n      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));\n      padding:10px;\n      cursor:pointer;\n      transition:transform .12s ease, border-color .12s ease, background .12s ease;\n      min-height:120px;\n      position:relative;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .portal{background:linear-gradient(180deg, rgba(2,6,23,.03), rgba(2,6,23,.01))}\n    .portal:hover{transform:translateY(-1px)}\n    .portal.active{\n      border-color:rgba(79,209,197,.55);\n      box-shadow:0 0 0 3px rgba(79,209,197,.12) inset;\n    }\n    .portal.locked{\n      border-color:rgba(245,158,11,.55);\n      box-shadow:0 0 0 3px rgba(245,158,11,.12) inset;\n    }\n    .pHead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}\n    .pTitle{font-size:12px;letter-spacing:.25px;color:var(--orange);}\n    .pBadge{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:3px 8px;\n      background:rgba(255,255,255,.03);\n      white-space:nowrap;\n    }\n    html[data-theme=\"light\"] .pBadge{background:rgba(2,6,23,.03)}\n    .pValue{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.02);\n      font-family:var(--mono);\n      font-size:12px;\n      color:var(--text);\n      white-space:pre-wrap;\n      word-break:break-word;\n      min-height:60px;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .pValue{background:rgba(2,6,23,.02)}\n    .pEmpty{color:var(--muted)}\n    .portalNote{margin-top:10px;color:var(--muted);font-size:12px}\n\n    .carouselWrap{\n      border:1px solid var(--line);\n      border-radius:16px;\n      background:rgba(255,255,255,.02);\n      padding:10px;\n      box-shadow:0 10px 28px rgba(0,0,0,.18);\n    }\n    html[data-theme=\"light\"] .carouselWrap{background:rgba(2,6,23,.02)}\n    .carouselTop{\n      display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;\n      margin-bottom:10px;\n    }\n    .carouselLeft{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n    .carouselRight{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}\n    .carouselCounter{\n      font-size:12px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n    }\n    html[data-theme=\"light\"] .carouselCounter{background:rgba(2,6,23,.03)}\n    .carouselDots{\n      display:flex;gap:6px;flex-wrap:wrap;align-items:center;\n      max-width:100%;\n    }\n    .dot{\n      width:9px;height:9px;border-radius:999px;\n      border:1px solid var(--line);\n      background:rgba(255,255,255,.02);\n      cursor:pointer;\n    }\n    html[data-theme=\"light\"] .dot{background:rgba(2,6,23,.02)}\n    .dot.active{\n      border-color:rgba(79,209,197,.65);\n      background:rgba(79,209,197,.25);\n      box-shadow:0 0 0 2px rgba(79,209,197,.10);\n    }\n    .carouselStage{\n      display:grid;\n      grid-template-columns:1fr;\n      gap:10px;\n      min-width:0;\n    }\n    .carouselHintRow{\n      margin-top:8px;\n      display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;\n      color:var(--muted);font-size:12px;\n    }\n    .jumpSelect{\n      font-size:12px;\n      border-radius:12px;\n      border:1px solid var(--line);\n      background:rgba(255,255,255,.03);\n      color:var(--text);\n      padding:8px 10px;\n      outline:none;\n      max-width:100%;\n    }\n    html[data-theme=\"light\"] .jumpSelect{background:rgba(2,6,23,.03)}\n    .kbdHint{\n      font-family:var(--mono);\n      font-size:11px;\n      border:1px dashed var(--line);\n      border-radius:10px;\n      padding:4px 8px;\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .kbdHint{background:rgba(2,6,23,.02)}\n\n    .modeBar{\n      display:flex;gap:6px;flex-wrap:wrap;align-items:center;\n      border:1px solid var(--line);\n      border-radius:999px;\n      padding:4px;\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .modeBar{background:rgba(2,6,23,.02)}\n    .modeBtn{\n      border:1px solid transparent;\n      background:transparent;\n      color:var(--muted);\n      padding:6px 10px;\n      border-radius:999px;\n      cursor:pointer;\n      font-size:12px;\n      line-height:1;\n      user-select:none;\n      white-space:nowrap;\n    }\n    .modeBtn:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.18)}\n    .modeBtn.active{\n      color:var(--text);\n      border-color:rgba(79,209,197,.45);\n      background:rgba(79,209,197,.10);\n    }\n    .modeTag{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n    }\n    html[data-theme=\"light\"] .modeTag{background:rgba(2,6,23,.03)}\n\n    .modalBg{\n      position:fixed;inset:0;background:rgba(0,0,0,.6);\n      display:none;align-items:center;justify-content:center;\n      z-index:9998;padding:14px;\n    }\n    .modal{\n      width:100%;\n      max-width:980px;\n      border:1px solid var(--line);\n      border-radius:16px;\n      background:var(--panel);\n      box-shadow:0 30px 90px rgba(0,0,0,.5);\n      overflow:hidden;\n    }\n    html[data-theme=\"light\"] .modal{background:#fff}\n    .modalTop{\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\n      padding:12px;border-bottom:1px solid var(--line);\n    }\n    .modalTop .t{font-size:13px}\n    .modalTop .s{font-size:12px;color:var(--muted)}\n\n    /* UPDATE: Portal Editor sub-header pack menu (additive) */\n    .packBar{\n      margin-top:8px;\n      display:flex;\n      gap:8px;\n      align-items:center;\n      flex-wrap:wrap;\n    }\n    .packLabel{\n      font-size:12px;\n      color:var(--muted);\n      white-space:nowrap;\n    }\n    .packChips{\n      display:flex;\n      gap:6px;\n      align-items:center;\n      overflow:auto;\n      max-width:100%;\n      padding:4px;\n      border:1px solid var(--line);\n      border-radius:999px;\n      background:rgba(255,255,255,.02);\n      -webkit-overflow-scrolling:touch;\n    }\n    html[data-theme=\"light\"] .packChips{background:rgba(2,6,23,.02)}\n    .packChip{\n      border:1px solid transparent;\n      background:transparent;\n      color:var(--muted);\n      padding:6px 10px;\n      border-radius:999px;\n      cursor:pointer;\n      font-size:12px;\n      line-height:1;\n      user-select:none;\n      white-space:nowrap;\n    }\n    .packChip:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.18)}\n    .packChip.active{\n      color:var(--text);\n      border-color:rgba(79,209,197,.45);\n      background:rgba(79,209,197,.10);\n    }\n    .packActivePill{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);\n      border-radius:999px;\n      padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n      white-space:nowrap;\n    }\n    html[data-theme=\"light\"] .packActivePill{background:rgba(2,6,23,.03)}\n\n    /* UPDATE #2: Continuity lock bar in Portal Editor (additive) */\n    .lockBar{\n      margin-top:8px;\n      display:none;\n      gap:8px;\n      align-items:center;\n      flex-wrap:wrap;\n    }\n    .lockPill{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n      white-space:nowrap;\n    }\n    html[data-theme=\"light\"] .lockPill{background:rgba(2,6,23,.03)}\n    .lockPill.engaged{\n      border-color:rgba(245,158,11,.55);\n      background:rgba(245,158,11,.10);\n      color:var(--text);\n    }\n\n    .modalBody{padding:12px;display:grid;grid-template-columns:1fr;gap:12px}\n    @media(min-width:980px){\n      .modalBody{grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr)}\n    }\n    .fieldLabel{font-size:12px;color:var(--muted);margin:0 0 6px 2px}\n    .modal textarea{\n      width:100%;min-height:220px;resize:vertical;\n      border:1px solid var(--line);border-radius:12px;padding:10px;\n      background:rgba(255,255,255,.03);color:var(--text);\n      font-family:var(--mono);font-size:12px;line-height:1.35;outline:none;\n    }\n    html[data-theme=\"light\"] .modal textarea{background:rgba(2,6,23,.03)}\n    .modal textarea[readonly]{\n      opacity:.9;\n      border-color:rgba(245,158,11,.45);\n      background:rgba(245,158,11,.05);\n      cursor:not-allowed;\n    }\n\n    .hookList{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.02);\n      max-height:260px;\n      overflow:auto;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .hookList{background:rgba(2,6,23,.02)}\n    .hookItem{\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\n      padding:8px;border:1px solid var(--line);border-radius:12px;\n      background:rgba(255,255,255,.02);\n      margin-bottom:8px;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .hookItem{background:rgba(2,6,23,.02)}\n    .hookItem:last-child{margin-bottom:0}\n    .hookText{font-family:var(--mono);font-size:12px;white-space:pre-wrap;word-break:break-word;min-width:0}\n    .hookBtns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}\n\n    pre{\n      margin:0;\n      white-space:pre-wrap;\n      word-break:break-word;\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.03);\n      color:var(--text);\n      font-family:var(--mono);\n      font-size:12px;\n      line-height:1.35;\n      max-height:320px;\n      overflow:auto;\n      min-width:0;\n    }\n    .hidden{display:none !important}\n\n    .logCompact .cardBody{padding:12px}\n    .logViewport{\n      border:1px solid var(--line);\n      border-radius:12px;\n      background:rgba(255,255,255,.02);\n      padding:10px;\n      max-height:220px;\n      overflow:auto;\n    }\n    html[data-theme=\"light\"] .logViewport{background:rgba(2,6,23,.02)}\n    .logEntry{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      margin-bottom:10px;\n      background:rgba(255,255,255,.02);\n    }\n    html[data-theme=\"light\"] .logEntry{background:rgba(2,6,23,.02)}\n    .logEntry:last-child{margin-bottom:0}\n    .logMeta{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between;margin-bottom:8px}\n    .logMeta .left{display:flex;gap:10px;flex-wrap:wrap;align-items:center}\n    .pill{\n      font-size:11px;color:var(--muted);\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\n      background:rgba(255,255,255,.03);\n      font-family:var(--mono);\n    }\n    html[data-theme=\"light\"] .pill{background:rgba(2,6,23,.03)}\n    .pill.locked{\n      border-color:rgba(245,158,11,.55);\n      background:rgba(245,158,11,.10);\n      color:var(--text);\n    }\n    .logActions{display:flex;gap:8px;flex-wrap:wrap}\n    .logPre{\n      white-space:pre-wrap;word-break:break-word;\n      font-family:var(--mono);font-size:12px;line-height:1.35;\n      margin:0;\n    }\n\n    .toastWrap{\n      position:fixed;left:12px; right:12px; bottom:12px;\n      display:flex; justify-content:center;\n      pointer-events:none;z-index:9999;\n    }\n    .toast{\n      pointer-events:none;\n      max-width:920px;width:100%;\n      border:1px solid var(--line);\n      border-radius:14px;\n      padding:10px 12px;\n      background:rgba(13,18,36,.92);\n      color:var(--text);\n      box-shadow:0 18px 60px rgba(0,0,0,.35);\n      display:flex; align-items:flex-start; justify-content:space-between; gap:12px;\n      transform:translateY(14px);\n      opacity:0;\n      transition:opacity .18s ease, transform .18s ease;\n    }\n    html[data-theme=\"light\"] .toast{background:rgba(255,255,255,.95)}\n    .toast.show{opacity:1; transform:translateY(0)}\n    .toast .msg{font-size:13px; line-height:1.3}\n    .toast .tag{\n      font-size:11px;color:var(--muted);white-space:nowrap;\n      border:1px solid var(--line);border-radius:999px;\n      padding:4px 8px;background:rgba(255,255,255,.03);\n    }\n    html[data-theme=\"light\"] .toast .tag{background:rgba(2,6,23,.03)}\n\n    .srOnly{\n      position:absolute !important;\n      width:1px;height:1px;\n      padding:0;margin:-1px;\n      overflow:hidden;clip:rect(0,0,0,0);\n      white-space:nowrap;border:0;\n    }\n    #legacyCopyArea{position:absolute;left:-9999px;top:-9999px}\n\n    .importsSummary{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-top:10px;}\n    .importsList{\n      border:1px solid var(--line);\n      border-radius:12px;\n      padding:10px;\n      background:rgba(255,255,255,.02);\n      max-height:320px;\n      overflow:auto;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .importsList{background:rgba(2,6,23,.02)}\n    .importRow{\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\n      padding:10px;border:1px solid var(--line);border-radius:12px;\n      background:rgba(255,255,255,.02);\n      margin-bottom:10px;\n      min-width:0;\n    }\n    html[data-theme=\"light\"] .importRow{background:rgba(2,6,23,.02)}\n    .importRow:last-child{margin-bottom:0}\n    .importMeta{min-width:0}\n    .importName{font-size:12px;color:var(--text);font-family:var(--mono);word-break:break-word}\n    .importSub{font-size:12px;color:var(--muted);margin-top:4px}\n    .importBtns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}\n    .miniNote{font-size:12px;color:var(--muted);line-height:1.35}\n\n    .receiptToggleBtn{\n      appearance:none;border:0;background:transparent;padding:0;margin:0;\n      color:var(--text);font-size:13px;letter-spacing:.2px;cursor:pointer;\n      display:inline-flex;align-items:center;gap:8px;\n    }\n    .receiptToggleBtn:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.22);border-radius:10px}\n    .chev{display:inline-block;width:16px;text-align:center;color:var(--muted);font-family:var(--mono);}\n    .receiptHeaderRight{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;}\n  </style>\n</head>\n\n<body>\n  <div class=\"wrap\">\n    <header class=\"topbar\" role=\"banner\">\n      <div class=\"brand\">\n        <h1>MH8-Music-Prompt-Pro (Ai Music-Creators-Console)</h1>\n        <div class=\"sub\">Vanilla SPA • Music portals + hooks import + triple-layer proof receipts</div>\n      </div>\n\n      <div class=\"controls\" aria-label=\"Top controls\">\n        <label class=\"srOnly\" for=\"platformSelect\">Platform</label>\n        <select id=\"platformSelect\" class=\"select\" aria-label=\"Select music platform\">\n          <option value=\"suno\">Suno</option>\n          <option value=\"udio\">Udio</option>\n          <option value=\"other\">Other / Custom</option>\n        </select>\n\n        <button id=\"importBtn\" class=\"btn secondary\" type=\"button\" aria-haspopup=\"dialog\">Import Hooks JSON</button>\n        <button id=\"importMgrBtn\" class=\"btn secondary icon small\" type=\"button\" aria-haspopup=\"dialog\" title=\"Manage imported JSON files\">☰ Imports</button>\n        <input id=\"importFile\" type=\"file\" accept=\".json,application/json,text/plain\" class=\"srOnly\" />\n\n        <button id=\"themeBtn\" class=\"btn secondary\" type=\"button\" aria-pressed=\"false\">Light Mode</button>\n        <button id=\"logBtn\" class=\"btn\" type=\"button\" aria-expanded=\"false\" aria-controls=\"logCard\">Log</button>\n      </div>\n    </header>\n\n    <div class=\"tabsBar\">\n      <div id=\"tablist\" class=\"tablist\" role=\"tablist\" aria-label=\"Prompt tabs\"></div>\n      <button id=\"addTabBtn\" class=\"btn small primary\" type=\"button\" aria-label=\"Add new prompt tab\">+ Add Prompt</button>\n    </div>\n\n    <main class=\"grid\" role=\"main\">\n      <section class=\"card\" aria-label=\"Prompt editor\">\n        <div class=\"cardHeader\">\n          <h2 class=\"headerOrange\">Music Prompt Editor — Entry Portals</h2>\n          <div class=\"hint\">Step portal-by-portal → stack hooks → Mint proof</div>\n        </div>\n\n        <div class=\"cardBody\">\n          <div class=\"carouselWrap\" aria-label=\"Entry portals carousel\">\n            <div class=\"carouselTop\">\n              <div class=\"carouselLeft\">\n                <span class=\"pill\">Flow</span>\n\n                <div class=\"modeBar\" role=\"group\" aria-label=\"Portal tier modes\">\n                  <button id=\"modeQuickBtn\" class=\"modeBtn\" type=\"button\" aria-pressed=\"false\">Quick Start</button>\n                  <button id=\"modeSemiBtn\" class=\"modeBtn\" type=\"button\" aria-pressed=\"false\">Semi Pro</button>\n                  <button id=\"modeAdvBtn\" class=\"modeBtn\" type=\"button\" aria-pressed=\"false\">Advanced</button>\n                </div>\n                <span id=\"modeLabelPill\" class=\"modeTag\">mode: QUICK</span>\n\n                <span id=\"portalCounter\" class=\"carouselCounter\">0/0</span>\n                <span class=\"kbdHint\">← →</span>\n                <span class=\"kbdHint\">Enter</span>\n              </div>\n              <div class=\"carouselRight\">\n                <label class=\"srOnly\" for=\"portalJump\">Jump to portal</label>\n                <select id=\"portalJump\" class=\"jumpSelect\" aria-label=\"Jump to portal\"></select>\n                <button id=\"portalPrevBtn\" class=\"btn small\" type=\"button\" aria-label=\"Previous portal\">◀ Prev</button>\n                <button id=\"portalNextBtn\" class=\"btn small primary\" type=\"button\" aria-label=\"Next portal\">Next ▶</button>\n              </div>\n            </div>\n\n            <div id=\"portalStage\" class=\"carouselStage\" aria-label=\"Active portal stage\"></div>\n\n            <div class=\"carouselHintRow\">\n              <div id=\"portalDots\" class=\"carouselDots\" aria-label=\"Portal progress dots\"></div>\n              <div style=\"display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:flex-end\">\n                <span style=\"color:var(--muted);font-size:12px\">Tip: Click portal card to edit. Hooks append in the modal.</span>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"portalNote\">\n            Active portal: <span id=\"activePortalName\" class=\"pill\">None</span>\n            <span id=\"continuityStatusPill\" class=\"pill\" style=\"margin-left:8px\">continuity: DISENGAGED</span>\n            <span style=\"margin-left:8px;color:var(--muted);font-size:12px\">Tip: Import hook packs, then inject hooks into any portal.</span>\n          </div>\n\n          <div class=\"actions\" role=\"group\" aria-label=\"Editor actions\">\n            <button id=\"mintBtn\" class=\"btn primary\" type=\"button\">Mint Proof Receipt</button>\n            <button id=\"copyBtn\" class=\"btn\" type=\"button\">Copy Receipt</button>\n            <button id=\"exportBtn\" class=\"btn\" type=\"button\">Export Receipt</button>\n            <button id=\"clearEditorBtn\" class=\"btn danger\" type=\"button\" title=\"Clears ALL portal entries in the current prompt\">Clear Editor</button>\n            <button id=\"clearReceiptBtn\" class=\"btn danger\" type=\"button\" title=\"Clears only the current on-screen receipt (does not delete vault log)\">Clear Receipt</button>\n          </div>\n\n          <div class=\"srOnly\" id=\"ariaStatus\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n        </div>\n      </section>\n\n      <section class=\"card\" aria-label=\"Receipt viewer\">\n        <div class=\"cardHeader\" id=\"receiptHeader\">\n          <button id=\"receiptToggleBtn\" class=\"receiptToggleBtn\" type=\"button\" aria-expanded=\"true\" aria-controls=\"receiptBody\">\n            <span class=\"chev\" id=\"receiptChev\">▾</span>\n            <span>Three-Layer Music Receipt</span>\n          </button>\n\n          <div class=\"receiptHeaderRight\">\n            <button id=\"promptEntriesBtn\" class=\"btn secondary small\" type=\"button\" aria-haspopup=\"dialog\" title=\"Shows Layer 1 + Layer 2 only (ready to paste)\">\n              PROMPT ENTRIES COMPLETED\n            </button>\n            <div class=\"hint\">Paste Layer 1 + 2 into Suno/Udio • Keep Layer 3 as proof</div>\n          </div>\n        </div>\n\n        <div class=\"cardBody\" id=\"receiptBody\">\n          <pre id=\"receiptPre\" aria-label=\"Minted receipt output\">No receipt minted yet.</pre>\n          <textarea id=\"legacyCopyArea\" aria-hidden=\"true\"></textarea>\n        </div>\n      </section>\n    </main>\n\n    <footer class=\"footerZone\" role=\"contentinfo\">\n      <section id=\"logCard\" class=\"card hidden logCompact\" aria-label=\"Minted receipt log\">\n        <div class=\"cardHeader\">\n          <h2>Minted Proof Log (Local)</h2>\n          <div class=\"hint\">Compact • scroll • copy per entry</div>\n        </div>\n        <div class=\"cardBody\">\n          <div class=\"actions\" role=\"group\" aria-label=\"Log actions\" style=\"margin-top:0;margin-bottom:10px\">\n            <button id=\"refreshLogBtn\" class=\"btn\" type=\"button\">Refresh Log</button>\n            <button id=\"exportVaultBtn\" class=\"btn secondary\" type=\"button\">Export Vault JSON</button>\n            <button id=\"clearVaultBtn\" class=\"btn danger\" type=\"button\" title=\"Deletes local vault log from this browser/device only\">Clear Vault (Local)</button>\n          </div>\n\n          <div id=\"logViewport\" class=\"logViewport\" aria-label=\"Vault log viewport\">\n            <div style=\"color:var(--muted);font-size:12px\">No receipts logged yet.</div>\n          </div>\n        </div>\n      </section>\n\n      <div class=\"copyrightFooter\">Copyrights Acbeatz.com IP Property 2026</div>\n    </footer>\n  </div>\n\n  <!-- Portal + Hooks modal -->\n  <div id=\"modalBg\" class=\"modalBg\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"modalTitle\" aria-hidden=\"true\">\n    <div class=\"modal\">\n      <div class=\"modalTop\">\n        <div style=\"min-width:0\">\n          <div id=\"modalTitle\" class=\"t\">Portal Editor</div>\n          <div id=\"modalSub\" class=\"s\">Click a hook to append, or type your custom entry.</div>\n\n          <!-- UPDATE: Sub-header menu inside Portal Editor to switch hook packs -->\n          <div class=\"packBar\" aria-label=\"Hook pack selector inside Portal Editor\">\n            <span class=\"packLabel\">Hook Pack:</span>\n            <div id=\"modalPackChips\" class=\"packChips\" role=\"group\" aria-label=\"Imported JSON hook packs (click to select)\"></div>\n            <span id=\"modalActivePackPill\" class=\"packActivePill\">active: MERGED</span>\n          </div>\n\n          <!-- UPDATE #2: Continuity Lock controls (only shown on CONTINUITY_LOCK portal) -->\n          <div id=\"lockBar\" class=\"lockBar\" aria-label=\"Continuity lock controls\">\n            <span id=\"lockStatusPill\" class=\"lockPill\">DISENGAGED</span>\n            <button id=\"lockAddBtn\" class=\"btn secondary small\" type=\"button\" title=\"Adds (snapshots) GENRE + BPM + KEY into Continuity Lock and engages lock\">ADD LOCK (GENRE+BPM+KEY)</button>\n            <button id=\"lockEngageBtn\" class=\"btn small primary\" type=\"button\" title=\"Engage continuity lock using current lock snapshot\">ENGAGE</button>\n            <button id=\"lockDisengageBtn\" class=\"btn small danger\" type=\"button\" title=\"Disengage continuity lock (unlocks GENRE/BPM/KEY editing)\">DISENGAGE</button>\n          </div>\n        </div>\n\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\">\n          <button id=\"modalAppendSelectedHookBtn\" class=\"btn secondary\" type=\"button\" title=\"Appends the selected hook into this portal\">Append Selected Hook</button>\n          <button id=\"modalSaveBtn\" class=\"btn primary\" type=\"button\">Save Portal</button>\n          <button id=\"modalCloseBtn\" class=\"btn danger\" type=\"button\">Close</button>\n        </div>\n      </div>\n\n      <div class=\"modalBody\">\n        <div>\n          <div class=\"fieldLabel\">Portal content (manual typing allowed)</div>\n          <textarea id=\"modalText\" spellcheck=\"false\" aria-label=\"Portal content editor\"></textarea>\n        </div>\n\n        <div>\n          <div class=\"fieldLabel\">Imported hooks menu (bracketed strings)</div>\n          <div id=\"hookList\" class=\"hookList\" aria-label=\"Hooks list\">\n            <div style=\"color:var(--muted);font-size:12px\">No hooks imported yet. Use “Import Hooks JSON”.</div>\n          </div>\n          <div style=\"margin-top:10px;color:var(--muted);font-size:12px\">\n            Active hook: <span id=\"activeHookLabel\" class=\"pill\">None</span>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Imports Manager modal -->\n  <div id=\"importsBg\" class=\"modalBg\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"importsTitle\" aria-hidden=\"true\">\n    <div class=\"modal\" style=\"max-width:980px\">\n      <div class=\"modalTop\">\n        <div>\n          <div id=\"importsTitle\" class=\"t\">Imports Manager — Hook Packs</div>\n          <div class=\"s\">Active hooks in the UI are the merged union of all imported JSON files.</div>\n        </div>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\">\n          <button id=\"importsMergeBtn\" class=\"btn secondary\" type=\"button\" title=\"Rebuild merged hooks from all imports\">Merge / Rebuild</button>\n          <button id=\"importsExportAllBtn\" class=\"btn secondary\" type=\"button\" title=\"Export all imported JSON files as a JSON string array for re-upload later\">Export All Imports</button>\n          <button id=\"importsClearAllBtn\" class=\"btn danger\" type=\"button\" title=\"Clear ALL imported JSON files and merged hooks from this device\">Clear All Imports</button>\n          <button id=\"importsCloseBtn\" class=\"btn danger\" type=\"button\">Close</button>\n        </div>\n      </div>\n\n      <div class=\"modalBody\" style=\"grid-template-columns:1fr\">\n        <div>\n          <div class=\"fieldLabel\">Active imports (this device)</div>\n\n          <div class=\"importsSummary\" aria-label=\"Imports summary\">\n            <span class=\"pill\">files: <span id=\"importsCountPill\">0</span></span>\n            <span class=\"pill\">merged hooks: <span id=\"importsMergedHooksPill\">0</span></span>\n            <span class=\"pill\">active in UI: <span id=\"importsActiveModePill\">MERGED</span></span>\n          </div>\n\n          <div style=\"margin-top:10px\" class=\"importsList\" id=\"importsList\" aria-label=\"Imported JSON files list\">\n            <div class=\"miniNote\">No imports yet. Use “Import Hooks JSON” to add one or more files.</div>\n          </div>\n\n          <div style=\"margin-top:10px\" class=\"miniNote\">\n            Export format is a JSON array of strings: each string is the original imported file text. Re-upload later to rebuild hook packs exactly.\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Prompt Entries Completed modal -->\n  <div id=\"promptEntriesBg\" class=\"modalBg\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"promptEntriesTitle\" aria-hidden=\"true\">\n    <div class=\"modal\" style=\"max-width:980px\">\n      <div class=\"modalTop\">\n        <div>\n          <div id=\"promptEntriesTitle\" class=\"t\">PROMPT ENTRIES COMPLETED</div>\n          <div class=\"s\">This window shows ONLY Layer 1 + Layer 2 (ready to paste). Layer 3 stays in the full receipt as proof.</div>\n        </div>\n        <div style=\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\">\n          <button id=\"promptEntriesCopyBtn\" class=\"btn secondary\" type=\"button\">Copy</button>\n          <button id=\"promptEntriesExportBtn\" class=\"btn secondary\" type=\"button\">Export</button>\n          <button id=\"promptEntriesClearBtn\" class=\"btn danger\" type=\"button\">Clear</button>\n          <button id=\"promptEntriesCloseBtn\" class=\"btn danger\" type=\"button\">Close</button>\n        </div>\n      </div>\n\n      <div class=\"modalBody\" style=\"grid-template-columns:1fr\">\n        <div>\n          <pre id=\"promptEntriesPre\" aria-label=\"Prompt entries completed output\">No prompt entries completed yet. Mint a receipt first.</pre>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <!-- Toast -->\n  <div class=\"toastWrap\" aria-hidden=\"false\">\n    <div id=\"toast\" class=\"toast\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\">\n      <div class=\"msg\" id=\"toastMsg\">Ready.</div>\n      <div class=\"tag\" id=\"toastTag\">MH8</div>\n    </div>\n  </div>\n\n  <script>\n  (function () {\n    \"use strict\";\n\n    // =========================\n    // MUSIC PORTALS (schema)\n    // =========================\n    // NOTE: All portals are text so hooks can be injected anywhere.\n    var PORTAL_KEYS = [\n      \"MODEL_BEHAVIOR_CONTROL\",\n      \"LYRICS\",\n      \"GENRE\",\n      \"MOOD\",\n      \"ENERGY\",\n      \"VOCAL_INTENSITY\",\n      \"ERA_INFLUENCE\",\n      \"BPM\",\n      \"KEY_SIGNATURE\",\n      \"BACKING_VOX\",\n      \"DRUMS\",\n      \"GUITAR\",\n      \"BASS\",\n      \"PIANO_KEYS\",\n      \"SYNTH\",\n      \"PERCUSSION\",\n      \"DYNAMICS\",\n      \"MIX\",\n      \"MASTER\",\n      \"EXTRA_HOOKS\",\n      \"NOTES\",\n      \"CONTINUITY_LOCK\",\n      \"SHA256_ID\"\n    ];\n\n    // Tier flows (flow-only; all portals still exist, mint/export/clear exactly the same)\n    var QUICK_KEYS = [\"MODEL_BEHAVIOR_CONTROL\",\"LYRICS\",\"GENRE\",\"MOOD\",\"ENERGY\",\"VOCAL_INTENSITY\",\"SHA256_ID\"];\n    var SEMI_KEYS  = [\"MODEL_BEHAVIOR_CONTROL\",\"LYRICS\",\"GENRE\",\"MOOD\",\"ENERGY\",\"VOCAL_INTENSITY\",\"ERA_INFLUENCE\",\"BPM\",\"KEY_SIGNATURE\",\"BACKING_VOX\",\"DRUMS\",\"BASS\",\"GUITAR\",\"SYNTH\",\"MIX\",\"MASTER\",\"EXTRA_HOOKS\",\"CONTINUITY_LOCK\",\"SHA256_ID\"];\n    var ADV_KEYS   = PORTAL_KEYS.slice(0);\n\n    // Optional: default copy block for NOTES (kept empty by default)\n    var DEFAULT_NOTES = \"\";\n\n    // =========================\n    // DOM\n    // =========================\n    var platformSelect = document.getElementById(\"platformSelect\");\n    var themeBtn = document.getElementById(\"themeBtn\");\n    var logBtn = document.getElementById(\"logBtn\");\n    var importBtn = document.getElementById(\"importBtn\");\n    var importMgrBtn = document.getElementById(\"importMgrBtn\");\n    var importFile = document.getElementById(\"importFile\");\n\n    var tablist = document.getElementById(\"tablist\");\n    var addTabBtn = document.getElementById(\"addTabBtn\");\n\n    var portalStage = document.getElementById(\"portalStage\");\n    var portalDots = document.getElementById(\"portalDots\");\n    var portalPrevBtn = document.getElementById(\"portalPrevBtn\");\n    var portalNextBtn = document.getElementById(\"portalNextBtn\");\n    var portalJump = document.getElementById(\"portalJump\");\n    var portalCounter = document.getElementById(\"portalCounter\");\n\n    var modeQuickBtn = document.getElementById(\"modeQuickBtn\");\n    var modeSemiBtn = document.getElementById(\"modeSemiBtn\");\n    var modeAdvBtn = document.getElementById(\"modeAdvBtn\");\n    var modeLabelPill = document.getElementById(\"modeLabelPill\");\n\n    var activePortalName = document.getElementById(\"activePortalName\");\n    var continuityStatusPill = document.getElementById(\"continuityStatusPill\");\n\n    var mintBtn = document.getElementById(\"mintBtn\");\n    var copyBtn = document.getElementById(\"copyBtn\");\n    var exportBtn = document.getElementById(\"exportBtn\");\n    var clearReceiptBtn = document.getElementById(\"clearReceiptBtn\");\n    var clearEditorBtn = document.getElementById(\"clearEditorBtn\");\n\n    var receiptPre = document.getElementById(\"receiptPre\");\n    var legacyCopyArea = document.getElementById(\"legacyCopyArea\");\n\n    var receiptToggleBtn = document.getElementById(\"receiptToggleBtn\");\n    var receiptChev = document.getElementById(\"receiptChev\");\n    var receiptBody = document.getElementById(\"receiptBody\");\n\n    var promptEntriesBtn = document.getElementById(\"promptEntriesBtn\");\n    var promptEntriesBg = document.getElementById(\"promptEntriesBg\");\n    var promptEntriesCloseBtn = document.getElementById(\"promptEntriesCloseBtn\");\n    var promptEntriesCopyBtn = document.getElementById(\"promptEntriesCopyBtn\");\n    var promptEntriesClearBtn = document.getElementById(\"promptEntriesClearBtn\");\n    var promptEntriesExportBtn = document.getElementById(\"promptEntriesExportBtn\");\n    var promptEntriesPre = document.getElementById(\"promptEntriesPre\");\n\n    var logCard = document.getElementById(\"logCard\");\n    var refreshLogBtn = document.getElementById(\"refreshLogBtn\");\n    var exportVaultBtn = document.getElementById(\"exportVaultBtn\");\n    var clearVaultBtn = document.getElementById(\"clearVaultBtn\");\n    var logViewport = document.getElementById(\"logViewport\");\n\n    var ariaStatus = document.getElementById(\"ariaStatus\");\n    var toast = document.getElementById(\"toast\");\n    var toastMsg = document.getElementById(\"toastMsg\");\n    var toastTag = document.getElementById(\"toastTag\");\n\n    var modalBg = document.getElementById(\"modalBg\");\n    var modalTitle = document.getElementById(\"modalTitle\");\n    var modalSub = document.getElementById(\"modalSub\");\n    var modalText = document.getElementById(\"modalText\");\n    var modalAppendSelectedHookBtn = document.getElementById(\"modalAppendSelectedHookBtn\");\n    var modalSaveBtn = document.getElementById(\"modalSaveBtn\");\n    var modalCloseBtn = document.getElementById(\"modalCloseBtn\");\n\n    var hookList = document.getElementById(\"hookList\");\n    var activeHookLabel = document.getElementById(\"activeHookLabel\");\n\n    // UPDATE: Portal Editor pack menu DOM\n    var modalPackChips = document.getElementById(\"modalPackChips\");\n    var modalActivePackPill = document.getElementById(\"modalActivePackPill\");\n\n    // UPDATE #2: Continuity lock modal controls DOM\n    var lockBar = document.getElementById(\"lockBar\");\n    var lockStatusPill = document.getElementById(\"lockStatusPill\");\n    var lockAddBtn = document.getElementById(\"lockAddBtn\");\n    var lockEngageBtn = document.getElementById(\"lockEngageBtn\");\n    var lockDisengageBtn = document.getElementById(\"lockDisengageBtn\");\n\n    var importsBg = document.getElementById(\"importsBg\");\n    var importsCloseBtn = document.getElementById(\"importsCloseBtn\");\n    var importsMergeBtn = document.getElementById(\"importsMergeBtn\");\n    var importsExportAllBtn = document.getElementById(\"importsExportAllBtn\");\n    var importsClearAllBtn = document.getElementById(\"importsClearAllBtn\");\n    var importsList = document.getElementById(\"importsList\");\n    var importsCountPill = document.getElementById(\"importsCountPill\");\n    var importsMergedHooksPill = document.getElementById(\"importsMergedHooksPill\");\n    var importsActiveModePill = document.getElementById(\"importsActiveModePill\");\n\n    // =========================\n    // STATE\n    // =========================\n    var state = {\n      platform: \"suno\",\n      theme: \"dark\",\n      showLog: false,\n      receiptText: \"\",\n      promptEntriesText: \"\",\n      receiptOpen: true,\n\n      tabs: [],\n      activeId: null,\n\n      hooks: [],\n      selectedHook: \"\",\n      activePortalKey: \"\",\n      modalPortalKey: \"\",\n\n      imports: [], // [{id, name, created_utc, raw_text, hooks_count}]\n      portal_mode: \"quick\", // quick | semi | adv\n\n      // UPDATE: Portal Editor pack selector state\n      modal_hook_pack_mode: \"merged\", // merged | single\n      modal_hook_pack_id: \"\" // import.id when single\n    };\n\n    // =========================\n    // Storage safe helpers\n    // =========================\n    function storageGet(key, fallback) {\n      try {\n        var v = localStorage.getItem(key);\n        if (v === null || v === undefined) return fallback;\n        return v;\n      } catch (e) { return fallback; }\n    }\n    function storageSet(key, value) { try { localStorage.setItem(key, value); return true; } catch (e) { return false; } }\n    function storageRemove(key) { try { localStorage.removeItem(key); return true; } catch (e) { return false; } }\n    function nowISO() { return new Date().toISOString(); }\n\n    // =========================\n    // Toast / ARIA\n    // =========================\n    var toastTimer = null;\n    function announce(msg) { ariaStatus.textContent = msg; }\n    function showToast(msg, tag) {\n      if (tag) toastTag.textContent = tag;\n      toastMsg.textContent = msg;\n      toast.className = \"toast show\";\n      announce(msg);\n      if (toastTimer) clearTimeout(toastTimer);\n      toastTimer = setTimeout(function () { toast.className = \"toast\"; }, 1800);\n    }\n\n    // =========================\n    // Theme\n    // =========================\n    function applyTheme(theme) {\n      state.theme = theme;\n      document.documentElement.setAttribute(\"data-theme\", theme);\n      var isDark = (theme === \"dark\");\n      themeBtn.setAttribute(\"aria-pressed\", isDark ? \"true\" : \"false\");\n      themeBtn.textContent = isDark ? \"Light Mode\" : \"Dark Mode\";\n      storageSet(\"mh8_music_theme\", theme);\n    }\n    function toggleTheme() {\n      applyTheme(state.theme === \"dark\" ? \"light\" : \"dark\");\n      showToast(\"Theme switched to \" + (state.theme === \"dark\" ? \"Dark\" : \"Light\") + \".\", \"THEME\");\n    }\n\n    // =========================\n    // Vault\n    // =========================\n    function getVault() {\n      var raw = storageGet(\"mh8_music_vault\", \"[]\");\n      try {\n        var arr = JSON.parse(raw);\n        if (Object.prototype.toString.call(arr) !== \"[object Array]\") return [];\n        return arr;\n      } catch (e) { return []; }\n    }\n    function setVault(arr) { return storageSet(\"mh8_music_vault\", JSON.stringify(arr)); }\n\n    function esc(s) {\n      s = String(s === undefined || s === null ? \"\" : s);\n      return s.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\").replace(/'/g,\"&#039;\");\n    }\n\n    function renderVaultEntry(entry, indexFromStart) {\n      var ts = entry && entry.created_utc ? entry.created_utc : \"\";\n      var sha = entry && entry.sha256 ? entry.sha256 : \"\";\n      var platform = entry && entry.payload && entry.payload.platform ? entry.payload.platform : \"\";\n      var name = (entry && entry.payload && entry.payload.prompt_name) ? entry.payload.prompt_name : (\"Prompt\");\n\n      var fullText =\n        (entry && entry.full_receipt) ? entry.full_receipt :\n        (entry && entry.receipt_text) ? (entry.receipt_text + \"\\n\\n\" + JSON.stringify(entry, null, 2)) :\n        JSON.stringify(entry, null, 2);\n\n      var id = \"logcopy_\" + String(indexFromStart);\n\n      return ''\n        + '<div class=\"logEntry\">'\n        +   '<div class=\"logMeta\">'\n        +     '<div class=\"left\">'\n        +       '<span class=\"pill\">' + esc(name) + '</span>'\n        +       '<span class=\"pill\">platform:' + esc(platform) + '</span>'\n        +       '<span class=\"pill\">ts:' + esc(ts) + '</span>'\n        +       '<span class=\"pill\">sha:' + esc(sha.slice(0,12)) + '…</span>'\n        +     '</div>'\n        +     '<div class=\"logActions\">'\n        +       '<button class=\"btn small\" type=\"button\" data-copyid=\"' + esc(id) + '\">Copy Entry</button>'\n        +     '</div>'\n        +   '</div>'\n        +   '<pre class=\"logPre\" id=\"' + esc(id) + '\">' + esc(fullText) + '</pre>'\n        + '</div>';\n    }\n\n    function bindLogCopyButtons() {\n      var btns = logViewport.querySelectorAll(\"button[data-copyid]\");\n      for (var i = 0; i < btns.length; i++) {\n        btns[i].onclick = function () {\n          var id = this.getAttribute(\"data-copyid\");\n          var el = document.getElementById(id);\n          if (!el) return;\n          copyAnyText(el.textContent || \"\");\n        };\n      }\n    }\n\n    function renderVault() {\n      var vault = getVault();\n      if (!vault.length) {\n        logViewport.innerHTML = '<div style=\"color:var(--muted);font-size:12px\">No receipts logged yet.</div>';\n        return;\n      }\n      var out = [];\n      for (var i = vault.length - 1; i >= 0; i--) out.push(renderVaultEntry(vault[i], i));\n      logViewport.innerHTML = out.join(\"\");\n      bindLogCopyButtons();\n    }\n\n    // =========================\n    // Tabs\n    // =========================\n    function uid() { return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); }\n\n    function makeEmptyPortals() {\n      var obj = {};\n      for (var i = 0; i < PORTAL_KEYS.length; i++) obj[PORTAL_KEYS[i]] = \"\";\n      obj.NOTES = DEFAULT_NOTES;\n\n      // UPDATE #2: Default continuity lock state visible + receipt-includable\n      obj.CONTINUITY_LOCK = \"DISENGAGED\";\n\n      // UPDATE #3: SHA256 ID portal default empty\n      if (typeof obj.SHA256_ID === \"undefined\") obj.SHA256_ID = \"\";\n\n      return obj;\n    }\n\n    function loadTabs() {\n      var raw = storageGet(\"mh8_music_tabs\", \"\");\n      var rawActive = storageGet(\"mh8_music_active\", \"\");\n      var tabs = null;\n\n      if (raw) { try { tabs = JSON.parse(raw); } catch (e) { tabs = null; } }\n\n      if (tabs && Object.prototype.toString.call(tabs) === \"[object Array]\" && tabs.length) {\n        var migrated = [];\n        for (var i = 0; i < tabs.length; i++) {\n          var t = tabs[i];\n          var portals = (t && t.portals) ? t.portals : makeEmptyPortals();\n\n          // UPDATE #2: Ensure continuity portal exists on older saves\n          if (portals && typeof portals.CONTINUITY_LOCK === \"undefined\") portals.CONTINUITY_LOCK = \"DISENGAGED\";\n\n          // UPDATE #3: Ensure SHA256_ID exists on older saves\n          if (portals && typeof portals.SHA256_ID === \"undefined\") portals.SHA256_ID = \"\";\n\n          migrated.push({\n            id: String(t.id || uid()),\n            name: String(t.name || (\"Prompt \" + (i + 1))),\n            portals: portals,\n\n            // UPDATE #2: continuity lock meta (additive; safe migration)\n            continuity_lock: (t && t.continuity_lock) ? t.continuity_lock : null\n          });\n        }\n        tabs = migrated;\n      }\n\n      if (!tabs || Object.prototype.toString.call(tabs) !== \"[object Array]\" || !tabs.length) {\n        tabs = [{ id: \"1\", name: \"Prompt 1\", portals: makeEmptyPortals(), continuity_lock: null }];\n        rawActive = \"1\";\n      }\n\n      state.tabs = tabs;\n      state.activeId = (rawActive && findTab(rawActive)) ? String(rawActive) : String(tabs[0].id);\n      persistTabs();\n    }\n\n    function persistTabs() {\n      storageSet(\"mh8_music_tabs\", JSON.stringify(state.tabs));\n      storageSet(\"mh8_music_active\", String(state.activeId));\n    }\n\n    function findTab(id) {\n      for (var i = 0; i < state.tabs.length; i++) {\n        if (String(state.tabs[i].id) === String(id)) return state.tabs[i];\n      }\n      return null;\n    }\n\n    function indexOfTab(id) {\n      for (var i = 0; i < state.tabs.length; i++) if (String(state.tabs[i].id) === String(id)) return i;\n      return 0;\n    }\n\n    function prevent(e) { if (!e) return; if (e.preventDefault) e.preventDefault(); e.returnValue = false; }\n\n    function setActiveTab(id, focusTabBtn) {\n      if (!findTab(id)) return;\n      state.activeId = String(id);\n      persistTabs();\n      renderTabs();\n      renderPortals();\n      if (focusTabBtn) {\n        var btn = document.getElementById(\"tabbtn_\" + state.activeId);\n        if (btn && btn.focus) btn.focus();\n      }\n      showToast(\"Active: \" + (findTab(state.activeId).name), \"TABS\");\n    }\n\n    function addTab() {\n      var id = uid();\n      var name = \"Prompt \" + (state.tabs.length + 1);\n      state.tabs.push({ id: id, name: name, portals: makeEmptyPortals(), continuity_lock: null });\n      setActiveTab(id, true);\n      showToast(\"New prompt created: \" + name + \".\", \"TABS\");\n    }\n\n    function deleteTab(id) {\n      if (state.tabs.length <= 1) { showToast(\"Cannot delete the last remaining prompt.\", \"TABS\"); return; }\n      var idx = -1;\n      for (var i = 0; i < state.tabs.length; i++) if (String(state.tabs[i].id) === String(id)) { idx = i; break; }\n      if (idx < 0) return;\n\n      var wasActive = (String(state.activeId) === String(id));\n      var name = state.tabs[idx].name;\n\n      state.tabs.splice(idx, 1);\n      if (wasActive) {\n        var newActive = state.tabs[Math.max(0, idx - 1)].id;\n        state.activeId = String(newActive);\n      }\n      persistTabs();\n      renderTabs();\n      renderPortals();\n      showToast(\"Deleted: \" + name + \".\", \"TABS\");\n    }\n\n    function renderTabs() {\n      while (tablist.firstChild) tablist.removeChild(tablist.firstChild);\n\n      for (var i = 0; i < state.tabs.length; i++) (function (tab) {\n        var wrap = document.createElement(\"div\");\n        wrap.className = \"tabWrap\";\n\n        var btn = document.createElement(\"button\");\n        btn.type = \"button\";\n        btn.className = \"tab\";\n        btn.id = \"tabbtn_\" + tab.id;\n        btn.setAttribute(\"role\", \"tab\");\n        btn.setAttribute(\"tabindex\", String(tab.id === state.activeId ? \"0\" : \"-1\"));\n        btn.setAttribute(\"aria-selected\", tab.id === state.activeId ? \"true\" : \"false\");\n        btn.textContent = tab.name;\n        btn.onclick = function () { setActiveTab(tab.id, true); };\n\n        btn.onkeydown = function (e) {\n          e = e || window.event;\n          var key = e.key || e.keyCode;\n          var idx = indexOfTab(tab.id);\n          if (key === \"ArrowLeft\" || key === 37) {\n            prevent(e);\n            var prev = (idx - 1 + state.tabs.length) % state.tabs.length;\n            setActiveTab(state.tabs[prev].id, true);\n          } else if (key === \"ArrowRight\" || key === 39) {\n            prevent(e);\n            var next = (idx + 1) % state.tabs.length;\n            setActiveTab(state.tabs[next].id, true);\n          } else if (key === \"Home\" || key === 36) {\n            prevent(e); setActiveTab(state.tabs[0].id, true);\n          } else if (key === \"End\" || key === 35) {\n            prevent(e); setActiveTab(state.tabs[state.tabs.length - 1].id, true);\n          }\n        };\n\n        var del = document.createElement(\"button\");\n        del.type = \"button\";\n        del.className = \"tabDel\";\n        del.setAttribute(\"aria-label\", \"Delete \" + tab.name);\n        del.title = \"Delete this prompt\";\n        del.innerHTML = \"✕\";\n        del.onclick = function (ev) {\n          if (ev && ev.preventDefault) ev.preventDefault();\n          deleteTab(tab.id);\n        };\n\n        wrap.appendChild(btn);\n        wrap.appendChild(del);\n        tablist.appendChild(wrap);\n      })(state.tabs[i]);\n    }\n\n    // =========================\n    // UPDATE #2: Continuity Lock (per-tab)\n    // =========================\n    function getTabContinuity(tab) {\n      if (!tab) return null;\n      if (!tab.continuity_lock || Object.prototype.toString.call(tab.continuity_lock) !== \"[object Object]\") {\n        tab.continuity_lock = {\n          engaged: false,\n          created_utc: \"\",\n          genre: \"\",\n          bpm: \"\",\n          key_signature: \"\"\n        };\n      }\n      if (typeof tab.continuity_lock.engaged !== \"boolean\") tab.continuity_lock.engaged = false;\n      tab.continuity_lock.genre = String(tab.continuity_lock.genre || \"\");\n      tab.continuity_lock.bpm = String(tab.continuity_lock.bpm || \"\");\n      tab.continuity_lock.key_signature = String(tab.continuity_lock.key_signature || \"\");\n      tab.continuity_lock.created_utc = String(tab.continuity_lock.created_utc || \"\");\n      return tab.continuity_lock;\n    }\n\n    function formatContinuityLockText(lock) {\n      lock = lock || { engaged:false, created_utc:\"\", genre:\"\", bpm:\"\", key_signature:\"\" };\n      if (!lock.engaged) return \"DISENGAGED\";\n      var out = [];\n      out.push(\"ENGAGED\");\n      out.push(\"LOCKED_UTC: \" + String(lock.created_utc || \"\"));\n      out.push(\"GENRE: \" + String(lock.genre || \"\").trim());\n      out.push(\"BPM: \" + String(lock.bpm || \"\").trim());\n      out.push(\"KEY_SIGNATURE: \" + String(lock.key_signature || \"\").trim());\n      return out.join(\"\\n\").trim();\n    }\n\n    function setContinuityLockPortalFromMeta(tab) {\n      if (!tab) return;\n      var lock = getTabContinuity(tab);\n      if (!tab.portals) tab.portals = makeEmptyPortals();\n      tab.portals.CONTINUITY_LOCK = formatContinuityLockText(lock);\n    }\n\n    function applyContinuityLockToPortals(tab) {\n      if (!tab || !tab.portals) return;\n      var lock = getTabContinuity(tab);\n      if (!lock.engaged) return;\n\n      // Enforce locked values into portals\n      tab.portals.GENRE = String(lock.genre || \"\");\n      tab.portals.BPM = String(lock.bpm || \"\");\n      tab.portals.KEY_SIGNATURE = String(lock.key_signature || \"\");\n      setContinuityLockPortalFromMeta(tab);\n    }\n\n    function continuityIsEngaged(tab) {\n      var lock = getTabContinuity(tab);\n      return !!(lock && lock.engaged);\n    }\n\n    function snapshotContinuityFromCurrent(tab) {\n      if (!tab || !tab.portals) return;\n      var lock = getTabContinuity(tab);\n      lock.created_utc = nowISO();\n      lock.genre = String(tab.portals.GENRE || \"\");\n      lock.bpm = String(tab.portals.BPM || \"\");\n      lock.key_signature = String(tab.portals.KEY_SIGNATURE || \"\");\n      lock.engaged = true;\n      applyContinuityLockToPortals(tab);\n      persistTabs();\n    }\n\n    function disengageContinuity(tab) {\n      if (!tab) return;\n      var lock = getTabContinuity(tab);\n      lock.engaged = false;\n      setContinuityLockPortalFromMeta(tab);\n      persistTabs();\n    }\n\n    function engageContinuity(tab) {\n      if (!tab) return;\n      var lock = getTabContinuity(tab);\n\n      // UPDATE #4: ALWAYS capture CURRENT portal inputs at ENGAGE time\n      // This guarantees GENRE/BPM/KEY_SIGNATURE are recognized and printed in receipts (not blank).\n      if (!tab.portals) tab.portals = makeEmptyPortals();\n      lock.genre = String(tab.portals.GENRE || \"\");\n      lock.bpm = String(tab.portals.BPM || \"\");\n      lock.key_signature = String(tab.portals.KEY_SIGNATURE || \"\");\n\n      if (!lock.created_utc) lock.created_utc = nowISO();\n      lock.engaged = true;\n      applyContinuityLockToPortals(tab);\n      persistTabs();\n    }\n\n    function renderContinuityStatusPill() {\n      if (!continuityStatusPill) return;\n      var tab = findTab(state.activeId);\n      if (!tab) { continuityStatusPill.textContent = \"continuity: DISENGAGED\"; continuityStatusPill.className = \"pill\"; return; }\n      var engaged = continuityIsEngaged(tab);\n      continuityStatusPill.textContent = \"continuity: \" + (engaged ? \"ENGAGED\" : \"DISENGAGED\");\n      continuityStatusPill.className = \"pill\" + (engaged ? \" locked\" : \"\");\n    }\n\n    // =========================\n    // Flow mode helpers\n    // =========================\n    function keysForMode(mode) {\n      mode = String(mode || \"\").toLowerCase();\n      if (mode === \"semi\") return SEMI_KEYS.slice(0);\n      if (mode === \"adv\") return ADV_KEYS.slice(0);\n      return QUICK_KEYS.slice(0);\n    }\n    function normalizeActivePortalForMode() {\n      var keys = keysForMode(state.portal_mode);\n      for (var i = 0; i < keys.length; i++) if (String(keys[i]) === String(state.activePortalKey)) return;\n      state.activePortalKey = keys.length ? keys[0] : \"LYRICS\";\n    }\n    function renderModeButtons() {\n      var m = String(state.portal_mode || \"quick\");\n      var isQ = (m === \"quick\"), isS = (m === \"semi\"), isA = (m === \"adv\");\n\n      modeQuickBtn.className = \"modeBtn\" + (isQ ? \" active\" : \"\");\n      modeSemiBtn.className  = \"modeBtn\" + (isS ? \" active\" : \"\");\n      modeAdvBtn.className   = \"modeBtn\" + (isA ? \" active\" : \"\");\n\n      modeQuickBtn.setAttribute(\"aria-pressed\", isQ ? \"true\" : \"false\");\n      modeSemiBtn.setAttribute(\"aria-pressed\",  isS ? \"true\" : \"false\");\n      modeAdvBtn.setAttribute(\"aria-pressed\",   isA ? \"true\" : \"false\");\n\n      modeLabelPill.textContent = \"mode: \" + (isQ ? \"QUICK\" : isS ? \"SEMI\" : \"ADV\");\n    }\n    function setPortalMode(mode, announceIt) {\n      mode = String(mode || \"\").toLowerCase();\n      if (mode !== \"quick\" && mode !== \"semi\" && mode !== \"adv\") mode = \"quick\";\n      state.portal_mode = mode;\n      storageSet(\"mh8_music_portal_mode\", mode);\n      normalizeActivePortalForMode();\n      renderModeButtons();\n      renderPortals();\n      if (announceIt) showToast(\"Flow mode: \" + (mode === \"quick\" ? \"Quick Start\" : mode === \"semi\" ? \"Semi Pro\" : \"Advanced Full Stack\") + \".\", \"FLOW\");\n    }\n\n    // =========================\n    // Portals render + edit (carousel)\n    // =========================\n    function portalIndexFromKey(key) {\n      var keys = keysForMode(state.portal_mode);\n      for (var i = 0; i < keys.length; i++) if (String(keys[i]) === String(key)) return i;\n      return 0;\n    }\n    function setActivePortalByIndex(idx, announceIt) {\n      var keys = keysForMode(state.portal_mode);\n      idx = Number(idx);\n      if (isNaN(idx)) idx = 0;\n      if (idx < 0) idx = 0;\n      if (idx >= keys.length) idx = keys.length - 1;\n      state.activePortalKey = keys[idx];\n      renderPortals();\n      if (announceIt) showToast(\"Portal: [\" + state.activePortalKey + \"].\", \"PORTAL\");\n    }\n    function goPrevPortal() { setActivePortalByIndex(portalIndexFromKey(state.activePortalKey) - 1, true); }\n    function goNextPortal() { setActivePortalByIndex(portalIndexFromKey(state.activePortalKey) + 1, true); }\n\n    function prettyPortalLabel(key) {\n      var map = {\n        MODEL_BEHAVIOR_CONTROL: \"MODEL BEHAVIOR CONTROL\",\n        LYRICS: \"LYRICS\",\n        GENRE: \"GENRE\",\n        MOOD: \"MOOD\",\n        ENERGY: \"ENERGY\",\n        VOCAL_INTENSITY: \"VOCAL INTENSITY\",\n        ERA_INFLUENCE: \"ERA / INFLUENCE\",\n        BPM: \"BPM\",\n        KEY_SIGNATURE: \"KEY SIGNATURE\",\n        BACKING_VOX: \"BACKING VOX\",\n        DRUMS: \"DRUMS\",\n        GUITAR: \"GUITAR\",\n        BASS: \"BASS\",\n        PIANO_KEYS: \"PIANO / KEYS\",\n        SYNTH: \"SYNTH\",\n        PERCUSSION: \"PERCUSSION\",\n        DYNAMICS: \"DYNAMICS\",\n        MIX: \"MIX\",\n        MASTER: \"MASTER\",\n        EXTRA_HOOKS: \"EXTRA HOOKS\",\n        NOTES: \"NOTES\",\n        CONTINUITY_LOCK: \"CONTINUITY LOCK\",\n        SHA256_ID: \"SHA256 ID\"\n      };\n      return map[key] || key;\n    }\n\n    function renderPortals() {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      // UPDATE #2: enforce lock into visible portals (no drift)\n      applyContinuityLockToPortals(tab);\n\n      // UPDATE #3: ensure SHA256_ID exists\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      normalizeActivePortalForMode();\n      renderModeButtons();\n\n      var keys = keysForMode(state.portal_mode);\n      if (!state.activePortalKey) state.activePortalKey = keys.length ? keys[0] : \"LYRICS\";\n      var idx = portalIndexFromKey(state.activePortalKey);\n      var key = keys[idx];\n      state.activePortalKey = key;\n\n      while (portalJump.firstChild) portalJump.removeChild(portalJump.firstChild);\n      for (var i = 0; i < keys.length; i++) {\n        var opt = document.createElement(\"option\");\n        opt.value = String(i);\n        opt.textContent = (i + 1) + \". [\" + prettyPortalLabel(keys[i]) + \"]\";\n        if (i === idx) opt.selected = true;\n        portalJump.appendChild(opt);\n      }\n\n      portalCounter.textContent = String(idx + 1) + \"/\" + String(keys.length);\n\n      while (portalDots.firstChild) portalDots.removeChild(portalDots.firstChild);\n      for (i = 0; i < keys.length; i++) (function (dotIndex) {\n        var d = document.createElement(\"button\");\n        d.type = \"button\";\n        d.className = \"dot\" + (dotIndex === idx ? \" active\" : \"\");\n        d.setAttribute(\"aria-label\", \"Go to portal \" + (dotIndex + 1));\n        d.onclick = function () { setActivePortalByIndex(dotIndex, false); };\n        portalDots.appendChild(d);\n      })(i);\n\n      while (portalStage.firstChild) portalStage.removeChild(portalStage.firstChild);\n\n      var val = (tab.portals && tab.portals[key]) ? String(tab.portals[key]) : \"\";\n\n      var portal = document.createElement(\"div\");\n\n      // UPDATE #2: mark locked portals when continuity lock engaged\n      var engaged = continuityIsEngaged(tab);\n      var isLockedPortal = engaged && (key === \"GENRE\" || key === \"BPM\" || key === \"KEY_SIGNATURE\");\n      portal.className = \"portal active\" + (isLockedPortal ? \" locked\" : \"\");\n      portal.setAttribute(\"role\", \"button\");\n      portal.setAttribute(\"tabindex\", \"0\");\n      portal.setAttribute(\"aria-label\", \"Portal \" + key + \". Click to edit.\");\n\n      var head = document.createElement(\"div\");\n      head.className = \"pHead\";\n\n      var title = document.createElement(\"div\");\n      title.className = \"pTitle\";\n      title.textContent = \"[\" + prettyPortalLabel(key) + \"]\";\n\n      var badge = document.createElement(\"div\");\n      badge.className = \"pBadge\";\n      if (isLockedPortal) badge.textContent = \"LOCKED\";\n      else badge.textContent = val ? (String(val.length) + \" chars\") : \"empty\";\n\n      head.appendChild(title);\n      head.appendChild(badge);\n\n      var body = document.createElement(\"div\");\n      body.className = \"pValue\";\n      if (!val) body.innerHTML = '<span class=\"pEmpty\">Click to add… (type or assign hooks)</span>';\n      else body.textContent = val;\n\n      portal.appendChild(head);\n      portal.appendChild(body);\n\n      portal.onclick = function () { openPortalModal(key); };\n      portal.onkeydown = function (e) {\n        e = e || window.event;\n        var k = e.key || e.keyCode;\n        if (k === \"Enter\" || k === \" \" || k === 13 || k === 32) { prevent(e); openPortalModal(key); }\n      };\n\n      portalStage.appendChild(portal);\n\n      activePortalName.textContent = \"[\" + prettyPortalLabel(key) + \"]\";\n\n      portalPrevBtn.disabled = (idx <= 0);\n      portalNextBtn.disabled = (idx >= keys.length - 1);\n\n      // UPDATE #2: continuity status in the editor footer row\n      renderContinuityStatusPill();\n    }\n\n    function updatePortalValue(portalKey, newValue) {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      if (!tab.portals) tab.portals = makeEmptyPortals();\n\n      // UPDATE #2: block updates to locked fields when engaged\n      if (continuityIsEngaged(tab) && (portalKey === \"GENRE\" || portalKey === \"BPM\" || portalKey === \"KEY_SIGNATURE\")) {\n        showToast(\"Continuity lock ENGAGED. Editing is locked for \" + portalKey + \". Disengage to edit.\", \"LOCK\");\n        applyContinuityLockToPortals(tab);\n        persistTabs();\n        renderPortals();\n        return;\n      }\n\n      tab.portals[portalKey] = String(newValue || \"\");\n\n      // UPDATE #2: keep continuity lock portal text in sync with meta (always)\n      if (portalKey === \"CONTINUITY_LOCK\") {\n        var lock = getTabContinuity(tab);\n        var txt = String(tab.portals.CONTINUITY_LOCK || \"\").trim().toUpperCase();\n        if (txt.indexOf(\"ENGAGED\") === 0) {\n          // If user manually typed ENGAGED, treat as engage (without snapshot change)\n          lock.engaged = true;\n          if (!lock.created_utc) lock.created_utc = nowISO();\n          setContinuityLockPortalFromMeta(tab);\n        } else if (txt.indexOf(\"DISENGAGED\") === 0 || txt === \"\") {\n          lock.engaged = false;\n          setContinuityLockPortalFromMeta(tab);\n        }\n      }\n\n      persistTabs();\n      renderPortals();\n    }\n\n    // =========================\n    // Modal\n    // =========================\n    var lastFocusEl = null;\n    var lastFocusElImports = null;\n    var lastFocusElPromptEntries = null;\n\n    // UPDATE: helpers for pack selection inside Portal Editor\n    function findImportById(id) {\n      for (var i = 0; i < state.imports.length; i++) if (String(state.imports[i].id) === String(id)) return state.imports[i];\n      return null;\n    }\n    function safeImportName(imp) {\n      var n = imp && imp.name ? String(imp.name) : \"import\";\n      if (n.length > 36) n = n.slice(0, 33) + \"…\";\n      return n;\n    }\n    function setModalHookPack(mode, id, announceIt) {\n      mode = String(mode || \"merged\");\n      if (mode !== \"merged\" && mode !== \"single\") mode = \"merged\";\n\n      if (mode === \"single\") {\n        var imp = findImportById(id);\n        if (!imp) mode = \"merged\";\n      }\n\n      state.modal_hook_pack_mode = mode;\n      state.modal_hook_pack_id = (mode === \"single\") ? String(id || \"\") : \"\";\n\n      renderHookPackMenu();\n      renderHookList();\n\n      if (announceIt) {\n        if (state.modal_hook_pack_mode === \"merged\") showToast(\"Hook pack set to MERGED (all).\", \"HOOKS\");\n        else {\n          var imp2 = findImportById(state.modal_hook_pack_id);\n          showToast(\"Hook pack selected: \" + (imp2 ? safeImportName(imp2) : \"import\") + \".\", \"HOOKS\");\n        }\n      }\n    }\n    function renderHookPackMenu() {\n      if (!modalPackChips) return;\n\n      while (modalPackChips.firstChild) modalPackChips.removeChild(modalPackChips.firstChild);\n\n      // Always include MERGED\n      var mergedBtn = document.createElement(\"button\");\n      mergedBtn.type = \"button\";\n      mergedBtn.className = \"packChip\" + (state.modal_hook_pack_mode === \"merged\" ? \" active\" : \"\");\n      mergedBtn.setAttribute(\"aria-label\", \"Select MERGED hooks (all imported packs)\");\n      mergedBtn.textContent = \"MERGED\";\n      mergedBtn.onclick = function () { setModalHookPack(\"merged\", \"\", true); };\n      modalPackChips.appendChild(mergedBtn);\n\n      // Add each imported file as a chip\n      for (var i = 0; i < state.imports.length; i++) (function (imp) {\n        var b = document.createElement(\"button\");\n        b.type = \"button\";\n        var isActive = (state.modal_hook_pack_mode === \"single\" && String(state.modal_hook_pack_id) === String(imp.id));\n        b.className = \"packChip\" + (isActive ? \" active\" : \"\");\n        b.setAttribute(\"aria-label\", \"Select hook pack \" + safeImportName(imp));\n        b.textContent = safeImportName(imp);\n        b.onclick = function () { setModalHookPack(\"single\", imp.id, true); };\n        modalPackChips.appendChild(b);\n      })(state.imports[i]);\n\n      // Active pill text\n      if (modalActivePackPill) {\n        if (state.modal_hook_pack_mode === \"merged\") {\n          modalActivePackPill.textContent = \"active: MERGED\";\n        } else {\n          var impA = findImportById(state.modal_hook_pack_id);\n          modalActivePackPill.textContent = \"active: \" + (impA ? safeImportName(impA) : \"MERGED\");\n          if (!impA) {\n            state.modal_hook_pack_mode = \"merged\";\n            state.modal_hook_pack_id = \"\";\n            modalActivePackPill.textContent = \"active: MERGED\";\n          }\n        }\n      }\n    }\n\n    // UPDATE #2: render continuity lock controls when applicable\n    function renderLockBarForPortal(portalKey) {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      var lock = getTabContinuity(tab);\n      var engaged = !!lock.engaged;\n\n      if (!lockBar) return;\n      if (String(portalKey) !== \"CONTINUITY_LOCK\") {\n        lockBar.style.display = \"none\";\n        return;\n      }\n\n      lockBar.style.display = \"flex\";\n      if (lockStatusPill) {\n        lockStatusPill.textContent = engaged ? \"ENGAGED\" : \"DISENGAGED\";\n        lockStatusPill.className = \"lockPill\" + (engaged ? \" engaged\" : \"\");\n      }\n      if (lockEngageBtn) lockEngageBtn.disabled = engaged;\n      if (lockDisengageBtn) lockDisengageBtn.disabled = !engaged;\n    }\n\n    function isPortalLockedByContinuity(portalKey) {\n      var tab = findTab(state.activeId);\n      if (!tab) return false;\n      if (!continuityIsEngaged(tab)) return false;\n      return (portalKey === \"GENRE\" || portalKey === \"BPM\" || portalKey === \"KEY_SIGNATURE\");\n    }\n\n    function openPortalModal(portalKey) {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      state.activePortalKey = portalKey;\n      state.modalPortalKey = portalKey;\n\n      // UPDATE #2: enforce lock before opening\n      applyContinuityLockToPortals(tab);\n\n      // UPDATE #3: ensure SHA256_ID exists before opening\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      var current = (tab.portals && tab.portals[portalKey]) ? String(tab.portals[portalKey]) : \"\";\n\n      modalTitle.textContent = \"Portal Editor — [\" + prettyPortalLabel(portalKey) + \"]\";\n\n      // UPDATE #2: locked portals are read-only\n      var locked = isPortalLockedByContinuity(portalKey);\n      if (locked) {\n        modalSub.textContent = \"Continuity lock is ENGAGED. This portal is locked (read-only). Disengage in CONTINUITY LOCK to edit.\";\n        modalText.setAttribute(\"readonly\",\"readonly\");\n      } else {\n        modalSub.textContent = \"Type your entry, or click a hook to assign into this portal.\";\n        modalText.removeAttribute(\"readonly\");\n      }\n\n      modalText.value = current;\n\n      lastFocusEl = document.activeElement;\n\n      modalBg.style.display = \"flex\";\n      modalBg.setAttribute(\"aria-hidden\", \"false\");\n\n      // UPDATE: render pack menu in Portal Editor\n      renderHookPackMenu();\n\n      // UPDATE #2: continuity lock controls\n      renderLockBarForPortal(portalKey);\n\n      renderHookList();\n\n      activePortalName.textContent = \"[\" + prettyPortalLabel(portalKey) + \"]\";\n      showToast(\"Portal selected: [\" + prettyPortalLabel(portalKey) + \"].\", \"PORTAL\");\n\n      setTimeout(function () { try { modalText.focus(); } catch (e) {} }, 0);\n    }\n\n    function closeModal() {\n      modalBg.style.display = \"none\";\n      modalBg.setAttribute(\"aria-hidden\", \"true\");\n      state.modalPortalKey = \"\";\n      try { if (lastFocusEl && lastFocusEl.focus) lastFocusEl.focus(); } catch (e) {}\n    }\n\n    modalCloseBtn.onclick = function () { closeModal(); };\n    modalBg.onclick = function (e) { e = e || window.event; if (e.target === modalBg) closeModal(); };\n\n    // Imports manager modal\n    function openImportsManager() {\n      lastFocusElImports = document.activeElement;\n      importsBg.style.display = \"flex\";\n      importsBg.setAttribute(\"aria-hidden\", \"false\");\n      renderImportsManager();\n      showToast(\"Imports Manager opened.\", \"IMPORTS\");\n      setTimeout(function(){ try { importsCloseBtn.focus(); } catch(e){} }, 0);\n    }\n    function closeImportsManager() {\n      importsBg.style.display = \"none\";\n      importsBg.setAttribute(\"aria-hidden\", \"true\");\n      try { if (lastFocusElImports && lastFocusElImports.focus) lastFocusElImports.focus(); } catch (e) {}\n    }\n    importsCloseBtn.onclick = function(){ closeImportsManager(); };\n    importsBg.onclick = function(e){ e = e || window.event; if (e.target === importsBg) closeImportsManager(); };\n\n    // Prompt Entries modal\n    function openPromptEntries() {\n      lastFocusElPromptEntries = document.activeElement;\n      promptEntriesBg.style.display = \"flex\";\n      promptEntriesBg.setAttribute(\"aria-hidden\", \"false\");\n      renderPromptEntries();\n      showToast(\"Prompt Entries module opened.\", \"PROMPT\");\n      setTimeout(function(){ try { promptEntriesCloseBtn.focus(); } catch(e){} }, 0);\n    }\n    function closePromptEntries() {\n      promptEntriesBg.style.display = \"none\";\n      promptEntriesBg.setAttribute(\"aria-hidden\", \"true\");\n      try { if (lastFocusElPromptEntries && lastFocusElPromptEntries.focus) lastFocusElPromptEntries.focus(); } catch (e) {}\n    }\n    promptEntriesCloseBtn.onclick = function(){ closePromptEntries(); };\n    promptEntriesBg.onclick = function(e){ e = e || window.event; if (e.target === promptEntriesBg) closePromptEntries(); };\n\n    // ESC closes whichever modal is on top\n    document.addEventListener(\"keydown\", function (e) {\n      var k = e.key || e.keyCode;\n      if (k === \"Escape\" || k === 27) {\n        if (promptEntriesBg.style.display === \"flex\") { if (e.preventDefault) e.preventDefault(); closePromptEntries(); return; }\n        if (importsBg.style.display === \"flex\") { if (e.preventDefault) e.preventDefault(); closeImportsManager(); return; }\n        if (modalBg.style.display === \"flex\") { if (e.preventDefault) e.preventDefault(); closeModal(); return; }\n      }\n    });\n\n    modalSaveBtn.onclick = function () {\n      if (!state.modalPortalKey) return;\n\n      // UPDATE #2: if locked, do not save edits\n      if (isPortalLockedByContinuity(state.modalPortalKey)) {\n        showToast(\"This portal is locked by Continuity Lock. Disengage to edit.\", \"LOCK\");\n        closeModal();\n        return;\n      }\n\n      updatePortalValue(state.modalPortalKey, modalText.value);\n      showToast(\"Saved portal [\" + prettyPortalLabel(state.modalPortalKey) + \"].\", \"PORTAL\");\n      closeModal();\n    };\n\n    modalAppendSelectedHookBtn.onclick = function () {\n      if (!state.modalPortalKey) return;\n\n      // UPDATE #2: if locked, block append\n      if (isPortalLockedByContinuity(state.modalPortalKey)) {\n        showToast(\"This portal is locked by Continuity Lock. Disengage to edit.\", \"LOCK\");\n        return;\n      }\n\n      if (!state.selectedHook) { showToast(\"No hook selected.\", \"HOOKS\"); return; }\n      var cur = modalText.value || \"\";\n      modalText.value = cur ? (cur + \"\\n\" + state.selectedHook) : state.selectedHook;\n      showToast(\"Hook appended to portal draft.\", \"HOOKS\");\n      try { modalText.focus(); } catch (e) {}\n    };\n\n    // UPDATE #2: Continuity lock modal buttons\n    if (lockAddBtn) lockAddBtn.onclick = function () {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      snapshotContinuityFromCurrent(tab);\n      setContinuityLockPortalFromMeta(tab);\n      renderLockBarForPortal(\"CONTINUITY_LOCK\");\n      renderContinuityStatusPill();\n      renderPortals();\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \"\";\n      showToast(\"Continuity lock ADDED + ENGAGED (GENRE+BPM+KEY).\", \"LOCK\");\n    };\n    if (lockEngageBtn) lockEngageBtn.onclick = function () {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      engageContinuity(tab); // UPDATE #4 now captures current portal inputs at engage time\n      setContinuityLockPortalFromMeta(tab);\n      renderLockBarForPortal(\"CONTINUITY_LOCK\");\n      renderContinuityStatusPill();\n      renderPortals();\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \"\";\n      showToast(\"Continuity lock ENGAGED.\", \"LOCK\");\n    };\n    if (lockDisengageBtn) lockDisengageBtn.onclick = function () {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      disengageContinuity(tab);\n      renderLockBarForPortal(\"CONTINUITY_LOCK\");\n      renderContinuityStatusPill();\n      renderPortals();\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \"\";\n      showToast(\"Continuity lock DISENGAGED.\", \"LOCK\");\n    };\n\n    // =========================\n    // Hooks import (bracketed strings)\n    // =========================\n    function parseHooksFromAnyJSON(obj) {\n      var hooks = [];\n      function addFromString(s) {\n        s = String(s || \"\");\n        var re = /\\[[^\\[\\]]+?\\]/g, m;\n        while ((m = re.exec(s)) !== null) hooks.push(m[0]);\n      }\n      function walk(v, depth) {\n        if (depth > 6) return;\n        if (v === null || v === undefined) return;\n        var t = Object.prototype.toString.call(v);\n        if (t === \"[object String]\") { addFromString(v); return; }\n        if (t === \"[object Array]\") { for (var i = 0; i < v.length; i++) walk(v[i], depth + 1); return; }\n        if (t === \"[object Object]\") { for (var k in v) if (Object.prototype.hasOwnProperty.call(v, k)) walk(v[k], depth + 1); return; }\n      }\n      walk(obj, 0);\n\n      var seen = {}, out = [];\n      for (var i = 0; i < hooks.length; i++) if (!seen[hooks[i]]) { seen[hooks[i]] = true; out.push(hooks[i]); }\n      return out;\n    }\n\n    // UPDATE: returns hooks for the selected pack in Portal Editor\n    function hooksForModalView() {\n      if (state.modal_hook_pack_mode === \"merged\") return (state.hooks && state.hooks.length) ? state.hooks : [];\n      var imp = findImportById(state.modal_hook_pack_id);\n      if (!imp || !imp.raw_text) return [];\n      // Cache parsed hooks on the import record (additive; safe)\n      if (imp._hooks_cache && Object.prototype.toString.call(imp._hooks_cache) === \"[object Array]\") return imp._hooks_cache;\n\n      var obj = null;\n      try { obj = JSON.parse(imp.raw_text); } catch (e) { obj = String(imp.raw_text || \"\"); }\n      var hooks = parseHooksFromAnyJSON(obj);\n      imp._hooks_cache = hooks;\n      return hooks;\n    }\n\n    function renderHookList() {\n      var listHooks = hooksForModalView();\n\n      if (!listHooks || !listHooks.length) {\n        // Friendly, accurate messaging based on modal selection\n        if (state.modal_hook_pack_mode === \"single\") {\n          var imp = findImportById(state.modal_hook_pack_id);\n          var label = imp ? safeImportName(imp) : \"import\";\n          hookList.innerHTML = '<div style=\"color:var(--muted);font-size:12px\">No bracketed hooks found in this pack: <b>' + esc(label) + '</b>. Switch to MERGED or import another file.</div>';\n        } else {\n          hookList.innerHTML = '<div style=\"color:var(--muted);font-size:12px\">No hook packs imported yet. Use “Import Hooks JSON”.</div>';\n        }\n        activeHookLabel.textContent = \"None\";\n        return;\n      }\n\n      var html = [];\n      for (var i = 0; i < listHooks.length; i++) {\n        var hook = listHooks[i];\n        html.push(\n          '<div class=\"hookItem\">'\n            + '<div class=\"hookText\">' + esc(hook) + '</div>'\n            + '<div class=\"hookBtns\">'\n              + '<button class=\"btn small\" type=\"button\" data-hook=\"' + esc(hook) + '\" data-action=\"select\">Select</button>'\n              + '<button class=\"btn small secondary\" type=\"button\" data-hook=\"' + esc(hook) + '\" data-action=\"append\">Append</button>'\n            + '</div>'\n          + '</div>'\n        );\n      }\n      hookList.innerHTML = html.join(\"\");\n      bindHookButtons();\n      activeHookLabel.textContent = state.selectedHook ? state.selectedHook : \"None\";\n    }\n\n    function bindHookButtons() {\n      var btns = hookList.querySelectorAll(\"button[data-action]\");\n      for (var i = 0; i < btns.length; i++) {\n        btns[i].onclick = function () {\n          var action = this.getAttribute(\"data-action\");\n          var hook = this.getAttribute(\"data-hook\") || \"\";\n          if (!hook) return;\n\n          if (action === \"select\") {\n            state.selectedHook = hook;\n            activeHookLabel.textContent = hook;\n            showToast(\"Hook selected.\", \"HOOKS\");\n          } else if (action === \"append\") {\n            state.selectedHook = hook;\n            activeHookLabel.textContent = hook;\n\n            if (modalBg.style.display === \"flex\" && state.modalPortalKey) {\n              // UPDATE #2: do not append into locked portals\n              if (isPortalLockedByContinuity(state.modalPortalKey)) {\n                showToast(\"This portal is locked by Continuity Lock. Disengage to edit.\", \"LOCK\");\n                return;\n              }\n              var cur = modalText.value || \"\";\n              modalText.value = cur ? (cur + \"\\n\" + hook) : hook;\n              showToast(\"Hook appended to portal draft.\", \"HOOKS\");\n              try { modalText.focus(); } catch (e) {}\n            } else {\n              showToast(\"Open a portal to append hooks.\", \"HOOKS\");\n            }\n          }\n        };\n      }\n    }\n\n    // =========================\n    // Imports Manager (multi-file)\n    // =========================\n    function getImportsStore() {\n      var raw = storageGet(\"mh8_music_imported_json_files_v1\", \"[]\");\n      try { var arr = JSON.parse(raw); return (Object.prototype.toString.call(arr) === \"[object Array]\") ? arr : []; }\n      catch (e) { return []; }\n    }\n    function setImportsStore(arr) { return storageSet(\"mh8_music_imported_json_files_v1\", JSON.stringify(arr)); }\n\n    function rebuildMergedHooksFromImports() {\n      var all = [], seen = {};\n      for (var i = 0; i < state.imports.length; i++) {\n        var imp = state.imports[i];\n        if (!imp || !imp.raw_text) continue;\n\n        // Clear modal cache on rebuild (additive, safe)\n        try { imp._hooks_cache = null; } catch (e) {}\n\n        var obj = null;\n        try { obj = JSON.parse(imp.raw_text); } catch (e) { obj = String(imp.raw_text || \"\"); }\n        var hooks = parseHooksFromAnyJSON(obj);\n        imp.hooks_count = hooks.length;\n\n        for (var j = 0; j < hooks.length; j++) {\n          var h = hooks[j];\n          if (!seen[h]) { seen[h] = true; all.push(h); }\n        }\n      }\n\n      state.hooks = all;\n      storageSet(\"mh8_music_imported_hooks\", JSON.stringify(all));\n      setImportsStore(state.imports);\n\n      // UPDATE: keep Portal Editor pack selection valid\n      if (state.modal_hook_pack_mode === \"single\" && state.modal_hook_pack_id) {\n        if (!findImportById(state.modal_hook_pack_id)) {\n          state.modal_hook_pack_mode = \"merged\";\n          state.modal_hook_pack_id = \"\";\n        }\n      }\n\n      if (modalBg.style.display === \"flex\") {\n        renderHookPackMenu();\n        renderHookList();\n      }\n      renderImportsManager();\n      return all.length;\n    }\n\n    function addImportFileRecord(filename, rawText) {\n      var rec = { id: uid(), name: String(filename || (\"import_\" + nowISO())), created_utc: nowISO(), raw_text: String(rawText || \"\"), hooks_count: 0 };\n      state.imports.push(rec);\n      var mergedCount = rebuildMergedHooksFromImports();\n      showToast(\"Hook pack added. Active merged hooks: \" + mergedCount + \".\", \"IMPORT\");\n    }\n\n    function deleteSingleImportById(id) {\n      var idx = -1;\n      for (var i = 0; i < state.imports.length; i++) if (String(state.imports[i].id) === String(id)) { idx = i; break; }\n      if (idx < 0) return;\n      var name = state.imports[idx].name;\n\n      state.imports.splice(idx, 1);\n\n      // UPDATE: if the deleted pack was active in Portal Editor, revert to MERGED\n      if (state.modal_hook_pack_mode === \"single\" && String(state.modal_hook_pack_id) === String(id)) {\n        state.modal_hook_pack_mode = \"merged\";\n        state.modal_hook_pack_id = \"\";\n      }\n\n      var mergedCount = rebuildMergedHooksFromImports();\n      showToast(\"Deleted import: \" + name + \". Merged hooks: \" + mergedCount + \".\", \"IMPORTS\");\n    }\n\n    function clearAllImports() {\n      state.imports = [];\n      state.hooks = [];\n      storageRemove(\"mh8_music_imported_json_files_v1\");\n      storageRemove(\"mh8_music_imported_hooks\");\n      state.selectedHook = \"\";\n\n      // UPDATE: reset Portal Editor pack selection\n      state.modal_hook_pack_mode = \"merged\";\n      state.modal_hook_pack_id = \"\";\n\n      renderHookPackMenu();\n      renderHookList();\n      renderImportsManager();\n      showToast(\"All hook packs cleared (local).\", \"IMPORTS\");\n    }\n\n    function exportAllImportsAsStringArray() {\n      var arr = [];\n      for (var i = 0; i < state.imports.length; i++) arr.push(String(state.imports[i].raw_text || \"\"));\n      var text = JSON.stringify(arr, null, 2);\n      var ok = downloadText(\"mh8-music-hookpacks-string-array.json\", text, \"application/json\");\n      showToast(ok ? \"All imports exported (string array).\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n\n    function renderImportsManager() {\n      var count = state.imports.length;\n      importsCountPill.textContent = String(count);\n      importsMergedHooksPill.textContent = String(state.hooks ? state.hooks.length : 0);\n      importsActiveModePill.textContent = \"MERGED\";\n\n      if (!count) {\n        importsList.innerHTML = '<div class=\"miniNote\">No imports yet. Use “Import Hooks JSON” to add one or more files.</div>';\n        return;\n      }\n\n      var html = [];\n      for (var i = 0; i < state.imports.length; i++) {\n        var imp = state.imports[i];\n        var name = imp.name || \"import\";\n        var ts = imp.created_utc || \"\";\n        var hc = imp.hooks_count || 0;\n        var bytes = (imp.raw_text && imp.raw_text.length) ? imp.raw_text.length : 0;\n\n        html.push(\n          '<div class=\"importRow\">'\n            + '<div class=\"importMeta\">'\n              + '<div class=\"importName\">' + esc(name) + '</div>'\n              + '<div class=\"importSub\">'\n                + 'hooks: <b>' + esc(hc) + '</b>'\n                + ' • chars: ' + esc(bytes)\n                + ' • ts: ' + esc(ts)\n              + '</div>'\n            + '</div>'\n            + '<div class=\"importBtns\">'\n              + '<button class=\"btn small\" type=\"button\" data-imp=\"' + esc(imp.id) + '\" data-impact=\"copy\">Copy JSON</button>'\n              + '<button class=\"btn small secondary\" type=\"button\" data-imp=\"' + esc(imp.id) + '\" data-impact=\"delete\">Delete</button>'\n            + '</div>'\n          + '</div>'\n        );\n      }\n\n      importsList.innerHTML = html.join(\"\");\n      bindImportsManagerButtons();\n    }\n\n    function bindImportsManagerButtons() {\n      var btns = importsList.querySelectorAll(\"button[data-impact]\");\n      for (var i = 0; i < btns.length; i++) {\n        btns[i].onclick = function () {\n          var act = this.getAttribute(\"data-impact\");\n          var id = this.getAttribute(\"data-imp\");\n          if (!id) return;\n\n          if (act === \"delete\") deleteSingleImportById(id);\n          else if (act === \"copy\") {\n            for (var j = 0; j < state.imports.length; j++) {\n              if (String(state.imports[j].id) === String(id)) {\n                copyAnyText(String(state.imports[j].raw_text || \"\"));\n                showToast(\"Imported JSON copied.\", \"COPY\");\n                break;\n              }\n            }\n          }\n        };\n      }\n    }\n\n    function handleImportedJSONText(text, filenameForManager) {\n      var obj = null;\n      try { obj = JSON.parse(text); } catch (e) { obj = String(text || \"\"); }\n\n      var hooks = parseHooksFromAnyJSON(obj);\n      if (!hooks.length) { showToast(\"No bracketed hooks found in import.\", \"IMPORT\"); return; }\n\n      addImportFileRecord(filenameForManager || \"hookpack.json\", String(text || \"\"));\n      if (modalBg.style.display === \"flex\") {\n        renderHookPackMenu();\n        renderHookList();\n      }\n    }\n\n    function importFromFile(file) {\n      try {\n        var reader = new FileReader();\n        reader.onload = function () { handleImportedJSONText(String(reader.result || \"\"), (file && file.name) ? file.name : \"hookpack.json\"); };\n        reader.onerror = function () { showToast(\"Import failed (file read).\", \"IMPORT\"); };\n        reader.readAsText(file);\n      } catch (e) { showToast(\"Import failed (unsupported).\", \"IMPORT\"); }\n    }\n\n    // =========================\n    // SHA-256 (crypto.subtle + fallback)\n    // =========================\n    function utf8Bytes(str) {\n      if (window.TextEncoder) return new TextEncoder().encode(str);\n      var utf8 = unescape(encodeURIComponent(str));\n      var arr = new Array(utf8.length);\n      for (var i = 0; i < utf8.length; i++) arr[i] = utf8.charCodeAt(i);\n      return arr;\n    }\n    function bytesToHex(bytes) {\n      var hex = \"\", i, b, h;\n      for (i = 0; i < bytes.length; i++) { b = bytes[i] & 255; h = b.toString(16); if (h.length < 2) h = \"0\" + h; hex += h; }\n      return hex;\n    }\n    function sha256Fallback(ascii) {\n      function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); }\n      var mathPow = Math.pow, maxWord = mathPow(2, 32), lengthProperty = \"length\";\n      var i, j, result = \"\", words = [], asciiBitLength = ascii[lengthProperty] * 8;\n      var hash = sha256Fallback.h = sha256Fallback.h || [];\n      var k = sha256Fallback.k = sha256Fallback.k || [];\n      var primeCounter = k[lengthProperty], isComposite = {};\n      for (var candidate = 2; primeCounter < 64; candidate++) {\n        if (!isComposite[candidate]) {\n          for (i = 0; i < 313; i += candidate) isComposite[i] = candidate;\n          hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;\n          k[primeCounter++] = (mathPow(candidate, 1/3) * maxWord) | 0;\n        }\n      }\n      ascii += \"\\x80\";\n      while (ascii[lengthProperty] % 64 - 56) ascii += \"\\x00\";\n      for (i = 0; i < ascii[lengthProperty]; i++) { j = ascii.charCodeAt(i); words[i >> 2] |= j << ((3 - i) % 4) * 8; }\n      words[words[lengthProperty]] = (asciiBitLength / maxWord) | 0;\n      words[words[lengthProperty]] = (asciiBitLength);\n      for (j = 0; j < words[lengthProperty];) {\n        var w = words.slice(j, j += 16), oldHash = hash.slice(0);\n        for (i = 0; i < 64; i++) {\n          var w15 = w[i - 15], w2 = w[i - 2];\n          var a = hash[0], e = hash[4];\n          var temp1 = hash[7]\n            + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25))\n            + ((e & hash[5]) ^ ((~e) & hash[6]))\n            + k[i]\n            + (w[i] = (i < 16) ? w[i] : (\n              w[i - 16]\n              + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3))\n              + w[i - 7]\n              + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))\n            ) | 0);\n          var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22))\n            + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));\n          hash = [(temp1 + temp2) | 0].concat(hash);\n          hash[4] = (hash[4] + temp1) | 0;\n          hash.pop();\n        }\n        for (i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;\n      }\n      for (i = 0; i < 8; i++) {\n        for (j = 3; j + 1; j--) {\n          var bb = (hash[i] >> (j * 8)) & 255;\n          result += ((bb < 16) ? \"0\" : \"\") + bb.toString(16);\n        }\n      }\n      return result;\n    }\n    function sha256Hex(message, cb) {\n      try {\n        if (window.crypto && window.crypto.subtle) {\n          var bytes = utf8Bytes(message);\n          var ab = bytes.buffer ? bytes.buffer : (new Uint8Array(bytes)).buffer;\n          window.crypto.subtle.digest(\"SHA-256\", ab).then(function (hashBuffer) {\n            cb(null, bytesToHex(new Uint8Array(hashBuffer)));\n          }).catch(function () {\n            cb(null, sha256Fallback(unescape(encodeURIComponent(message))));\n          });\n          return;\n        }\n      } catch (e) {}\n      cb(null, sha256Fallback(unescape(encodeURIComponent(message))));\n    }\n\n    // =========================\n    // Deterministic JSON (stable order)\n    // =========================\n    function stableStringify(value) {\n      var t = Object.prototype.toString.call(value);\n      if (t === \"[object Null]\" || t === \"[object Undefined]\") return \"null\";\n      if (t === \"[object Number]\" || t === \"[object Boolean]\") return JSON.stringify(value);\n      if (t === \"[object String]\") return JSON.stringify(String(value));\n      if (t === \"[object Array]\") {\n        var a = [];\n        for (var i = 0; i < value.length; i++) a.push(stableStringify(value[i]));\n        return \"[\" + a.join(\",\") + \"]\";\n      }\n      if (t === \"[object Object]\") {\n        var keys = [];\n        for (var k in value) if (Object.prototype.hasOwnProperty.call(value, k)) keys.push(k);\n        keys.sort();\n        var parts = [];\n        for (i = 0; i < keys.length; i++) parts.push(JSON.stringify(keys[i]) + \":\" + stableStringify(value[keys[i]]));\n        return \"{\" + parts.join(\",\") + \"}\";\n      }\n      return JSON.stringify(String(value));\n    }\n\n    // =========================\n    // Receipt + Prompt Entries\n    // =========================\n    function setReceiptText(text) {\n      state.receiptText = text || \"\";\n      receiptPre.textContent = state.receiptText ? state.receiptText : \"No receipt minted yet.\";\n      if (state.receiptText) storageSet(\"mh8_music_receipt\", state.receiptText);\n      else storageRemove(\"mh8_music_receipt\");\n    }\n\n    function setPromptEntriesText(text) {\n      state.promptEntriesText = text || \"\";\n      if (state.promptEntriesText) storageSet(\"mh8_music_prompt_entries\", state.promptEntriesText);\n      else storageRemove(\"mh8_music_prompt_entries\");\n      renderPromptEntries();\n    }\n\n    function renderPromptEntries() {\n      var t = state.promptEntriesText ? state.promptEntriesText : \"No prompt entries completed yet. Mint a receipt first.\";\n      promptEntriesPre.textContent = t;\n    }\n\n    // Layer 1: Lyrics (paste)\n    function buildLyricsSection(portals) {\n      var lyrics = String((portals && portals.LYRICS) ? portals.LYRICS : \"\").trim();\n      return lyrics;\n    }\n\n    // Layer 2: Style / tags / settings (paste)\n    function buildStyleSection(platform, portals) {\n      platform = String(platform || \"suno\");\n      var p = portals || {};\n\n      function line(lbl, v) {\n        v = String(v || \"\").trim();\n        if (!v) return null;\n        return \"• [\" + lbl + \"] \" + v;\n      }\n\n      var lines = [];\n      lines.push(\"• [PLATFORM] \" + (platform === \"suno\" ? \"SUNO\" : platform === \"udio\" ? \"UDIO\" : \"CUSTOM\"));\n\n      var l;\n      l = line(\"MODEL_BEHAVIOR_CONTROL\", p.MODEL_BEHAVIOR_CONTROL); if (l) lines.push(l);\n\n      l = line(\"GENRE\", p.GENRE); if (l) lines.push(l);\n      l = line(\"MOOD\", p.MOOD); if (l) lines.push(l);\n      l = line(\"ENERGY\", p.ENERGY); if (l) lines.push(l);\n      l = line(\"VOCAL_INTENSITY\", p.VOCAL_INTENSITY); if (l) lines.push(l);\n      l = line(\"ERA\", p.ERA_INFLUENCE); if (l) lines.push(l);\n      l = line(\"BPM\", p.BPM); if (l) lines.push(l);\n      l = line(\"KEY_SIGNATURE\", p.KEY_SIGNATURE); if (l) lines.push(l);\n\n      l = line(\"BACKING_VOX\", p.BACKING_VOX); if (l) lines.push(l);\n      l = line(\"DRUMS\", p.DRUMS); if (l) lines.push(l);\n      l = line(\"GUITAR\", p.GUITAR); if (l) lines.push(l);\n      l = line(\"BASS\", p.BASS); if (l) lines.push(l);\n      l = line(\"PIANO_KEYS\", p.PIANO_KEYS); if (l) lines.push(l);\n      l = line(\"SYNTH\", p.SYNTH); if (l) lines.push(l);\n      l = line(\"PERCUSSION\", p.PERCUSSION); if (l) lines.push(l);\n\n      l = line(\"DYNAMICS\", p.DYNAMICS); if (l) lines.push(l);\n      l = line(\"MIX\", p.MIX); if (l) lines.push(l);\n      l = line(\"MASTER\", p.MASTER); if (l) lines.push(l);\n\n      var lock = String(p.CONTINUITY_LOCK || \"\").trim();\n      if (lock) lines.push(\"• [CONTINUITY_LOCK] \" + lock.replace(/\\r?\\n/g, \" | \"));\n\n      // UPDATE #3: include SHA256 ID portal entry in Layer 2\n      l = line(\"SHA256_ID\", p.SHA256_ID); if (l) lines.push(l);\n\n      var xh = String(p.EXTRA_HOOKS || \"\").trim();\n      if (xh) {\n        lines.push(\"\");\n        lines.push(\"• [EXTRA_HOOKS] (sealed)\");\n        var xl = xh.split(/\\r?\\n/).map(function(s){ return s.trim(); }).filter(Boolean);\n        for (var i = 0; i < xl.length; i++) lines.push(\"  \" + xl[i]);\n      }\n\n      var notes = String(p.NOTES || \"\").trim();\n      if (notes) {\n        lines.push(\"\");\n        lines.push(\"• [NOTES]\");\n        var nl = notes.split(/\\r?\\n/);\n        for (i = 0; i < nl.length; i++) lines.push(\"  \" + String(nl[i]));\n      }\n\n      return lines.join(\"\\n\").trim();\n    }\n\n    // UPDATE #5: include minted SHA256 as LAST bracketed entry in Prompt Entries Completed\n    function buildPromptEntriesCompleted(layer1, layer2, mintedSha256Hex) {\n      var out = [];\n      out.push(\"[PROMPT ENTRIES COMPLETED — COPY/PASTE]\");\n      out.push(\"INSTRUCTIONS: Copy/paste the two blocks below into your AI music platform. Layer 3 is NOT included here.\");\n      out.push(\"\");\n      out.push(\"[LAYER 1 — LYRICS (PASTE THIS)]\");\n      out.push(\"Paste into the platform’s LYRICS field.\");\n      out.push(\"\");\n      out.push(layer1 || \"\");\n      out.push(\"\");\n      out.push(\"[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\");\n      out.push(\"Paste into the platform’s STYLE / PROMPT / TAGS field.\");\n      out.push(\"\");\n      out.push(layer2 || \"\");\n\n      // REQUIRED: last entry\n      out.push(\"\");\n      out.push(\"[SHA 256 ID + \" + String(mintedSha256Hex || \"\").trim() + \"]\");\n\n      return out.join(\"\\n\").replace(/\\n{3,}/g, \"\\n\\n\");\n    }\n\n    function mintReceipt() {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n\n      // UPDATE #2: enforce continuity lock before minting (ensures receipts reflect locked values)\n      applyContinuityLockToPortals(tab);\n\n      // UPDATE #3: ensure SHA256_ID exists before minting\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      var platform = String(state.platform);\n      var ts = nowISO();\n      var brand = \"ACBEATZ.COM\";\n\n      var portals = tab.portals || makeEmptyPortals();\n      var layer1 = buildLyricsSection(portals);\n      var layer2 = buildStyleSection(platform, portals);\n\n      var corePayload = {\n        mh8_system: \"MH8-AI-MUSIC-PROOF-OF-CREATION-VAULT\",\n        receipt_type: \"MH8-PROMPT-TRIPLE-LAYER\",\n        receipt_version: \"2.0.0\",\n        created_utc: ts,\n        brand: brand,\n        platform: platform,\n        prompt_name: tab.name,\n        layers: {\n          layer_1_lyrics: layer1,\n          layer_2_style: layer2\n        },\n        portals: portals,\n\n        // UPDATE #2: continuity meta is included in receipt payload (additive)\n        continuity_lock: getTabContinuity(tab),\n\n        imported_hooks_count: state.hooks ? state.hooks.length : 0\n      };\n\n      var canonical = stableStringify(corePayload);\n      var hashInput = \"MH8-Acbeatz.com|\" + canonical;\n\n      mintBtn.disabled = true;\n      mintBtn.textContent = \"Minting...\";\n\n      sha256Hex(hashInput, function (err, hash) {\n        mintBtn.disabled = false;\n        mintBtn.textContent = \"Mint Proof Receipt\";\n        if (err) { showToast(\"Mint failed. (Hash error)\", \"MINT\"); return; }\n\n        var humanPretty =\n          \"MH8-Acbeatz.com — MH8 AI Music Proof-of-Creation Vault\\n\" +\n          \"Receipt: Prompt (triple-layer)\\n\" +\n          \"Logic: Lyrics + Style + Hooks cryptographically sealed\\n\" +\n          \"Timestamp: \" + ts + \"\\n\" +\n          \"SHA256: \" + hash + \"\\n\" +\n          \"Brand: \" + brand + \"\\n\" +\n          \"Crypto receipt: ✅ Complete (local SHA-256)\\n\" +\n          \"Integrity rule: NON-COPIABLE WHEN HASH-CHAIN BROKEN\\n\" +\n          \"© 2026 ACBEATZ.COM — All rights reserved.\";\n\n        var receiptDisplay = [];\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"LAYER 1 — LYRICS (PASTE THIS)\");\n        receiptDisplay.push(\"INSTRUCTIONS: Paste ONLY this layer into the AI music platform’s LYRICS field.\");\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(layer1 || \"\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)\");\n        receiptDisplay.push(\"INSTRUCTIONS: Paste ONLY this layer into the platform’s STYLE / PROMPT / TAGS field.\");\n        receiptDisplay.push(\"NOTE: This includes continuity lock + extra hooks + SHA256 ID if provided.\");\n        receiptDisplay.push(\"##==============================\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(layer2 || \"\");\n        receiptDisplay.push(\"\");\n        receiptDisplay.push(\"=========================================\");\n        receiptDisplay.push(\"LAYER 3 — HASHED RECEIPT (DO NOT PASTE)\");\n        receiptDisplay.push(\"INSTRUCTIONS: Do NOT paste this into any AI music platform.\");\n        receiptDisplay.push(\"Keep it as proof-of-creation for audit/dispute/reference.\");\n        receiptDisplay.push(\"=========================================\");\n        receiptDisplay.push(\"\");\n\n        var machineHard = {\n          mh8_system: \"ACBEATZ.COM\",\n          brand: brand,\n          created_utc: ts,\n          integrity_rule: \"NON-COPIABLE WHEN HASH-CHAIN BROKEN\",\n          core_payload: corePayload,\n          canonical_payload: canonical,\n          hash_input: hashInput,\n          sha256_hex: hash,\n          cryptographic_receipt_status: \"VERIFIED_LOCAL\"\n        };\n\n        var finalObj = {\n          human_pretty: humanPretty,\n          machine_hard: machineHard,\n          created_utc: ts,\n          sha256: hash,\n          payload: {\n            platform: platform,\n            prompt_name: tab.name,\n            layer_1_lyrics: layer1,\n            layer_2_style: layer2,\n            portals: portals,\n\n            // UPDATE #2: explicit continuity snapshot in payload mirror (additive)\n            continuity_lock: getTabContinuity(tab)\n          },\n          receipt_text: receiptDisplay.join(\"\\n\")\n        };\n\n        var fullReceipt = finalObj.receipt_text + \"\\n\\n\" + JSON.stringify(finalObj, null, 2);\n        finalObj.full_receipt = fullReceipt;\n\n        setReceiptText(fullReceipt);\n\n        // UPDATE #5: pass minted SHA256 into Prompt Entries Completed and append as LAST line\n        var pe = buildPromptEntriesCompleted(layer1, layer2, hash);\n        setPromptEntriesText(pe);\n\n        var vault = getVault();\n        vault.push(finalObj);\n        var ok = setVault(vault);\n        showToast(ok ? \"Receipt minted + logged locally.\" : \"Receipt minted. Log storage blocked (device policy).\", \"MINT\");\n        if (state.showLog) renderVault();\n      });\n    }\n\n    // =========================\n    // Copy helpers\n    // =========================\n    function fallbackCopyText(text) {\n      try {\n        legacyCopyArea.value = text;\n        legacyCopyArea.style.display = \"block\";\n        legacyCopyArea.select();\n        legacyCopyArea.setSelectionRange(0, legacyCopyArea.value.length);\n        var ok = document.execCommand(\"copy\");\n        legacyCopyArea.style.display = \"none\";\n        return ok;\n      } catch (e) {\n        try { legacyCopyArea.style.display = \"none\"; } catch (e2) {}\n        return false;\n      }\n    }\n    function copyAnyText(text) {\n      if (!text) return;\n      try {\n        if (navigator.clipboard && window.isSecureContext) {\n          navigator.clipboard.writeText(text).then(function () {\n            showToast(\"Copied to clipboard.\", \"COPY\");\n          }).catch(function () {\n            var ok = fallbackCopyText(text);\n            showToast(ok ? \"Copied (legacy mode).\" : \"Copy failed (browser restriction).\", \"COPY\");\n          });\n          return;\n        }\n      } catch (e) {}\n      var ok2 = fallbackCopyText(text);\n      showToast(ok2 ? \"Copied (legacy mode).\" : \"Copy failed (browser restriction).\", \"COPY\");\n    }\n\n    function copyReceipt() {\n      if (!state.receiptText) { showToast(\"Nothing to copy. Mint a receipt first.\", \"COPY\"); return; }\n      copyAnyText(state.receiptText);\n    }\n\n    // =========================\n    // Export\n    // =========================\n    function downloadText(filename, text, mime) {\n      mime = mime || \"application/octet-stream\";\n      try {\n        var blob = new Blob([text], { type: mime });\n        var a = document.createElement(\"a\");\n        a.href = URL.createObjectURL(blob);\n        a.download = filename;\n        document.body.appendChild(a);\n        a.click();\n        setTimeout(function () {\n          URL.revokeObjectURL(a.href);\n          document.body.removeChild(a);\n        }, 0);\n        return true;\n      } catch (e) {\n        try {\n          var a2 = document.createElement(\"a\");\n          a2.href = \"data:\" + mime + \";charset=utf-8,\" + encodeURIComponent(text);\n          a2.download = filename;\n          document.body.appendChild(a2);\n          a2.click();\n          document.body.removeChild(a2);\n          return true;\n        } catch (e2) { return false; }\n      }\n    }\n\n    function exportReceipt() {\n      if (!state.receiptText) { showToast(\"Nothing to export. Mint a receipt first.\", \"EXPORT\"); return; }\n      var ok = downloadText(\"mh8-music-receipt.txt\", state.receiptText, \"text/plain\");\n      showToast(ok ? \"Receipt exported.\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n\n    function exportVault() {\n      var vault = getVault();\n      var text = vault.length ? JSON.stringify(vault, null, 2) : \"[]\";\n      var ok = downloadText(\"mh8-music-vault.json\", text, \"application/json\");\n      showToast(ok ? \"Vault exported as JSON.\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n\n    function copyPromptEntries() {\n      if (!state.promptEntriesText) { showToast(\"Nothing to copy. Mint a receipt first.\", \"COPY\"); return; }\n      copyAnyText(state.promptEntriesText);\n    }\n    function exportPromptEntries() {\n      if (!state.promptEntriesText) { showToast(\"Nothing to export. Mint a receipt first.\", \"EXPORT\"); return; }\n      var ok = downloadText(\"mh8-music-prompt-entries-completed.txt\", state.promptEntriesText, \"text/plain\");\n      showToast(ok ? \"Prompt entries exported.\" : \"Export blocked by browser.\", \"EXPORT\");\n    }\n    function clearPromptEntries() {\n      setPromptEntriesText(\"\");\n      showToast(\"Prompt entries cleared.\", \"PROMPT\");\n    }\n\n    // =========================\n    // Log toggle\n    // =========================\n    function setLogVisible(visible) {\n      state.showLog = !!visible;\n      if (state.showLog) {\n        logCard.className = \"card logCompact\";\n        logBtn.setAttribute(\"aria-expanded\", \"true\");\n        renderVault();\n        showToast(\"Log opened (compact + scroll).\", \"LOG\");\n      } else {\n        logCard.className = \"card hidden logCompact\";\n        logBtn.setAttribute(\"aria-expanded\", \"false\");\n        showToast(\"Log closed.\", \"LOG\");\n      }\n      storageSet(\"mh8_music_show_log\", state.showLog ? \"1\" : \"0\");\n    }\n\n    // =========================\n    // Clear editor\n    // =========================\n    function clearEditor() {\n      var tab = findTab(state.activeId);\n      if (!tab) return;\n      tab.portals = makeEmptyPortals();\n\n      // UPDATE #2: clear lock meta as well (still additive behavior; matches \"reset\")\n      tab.continuity_lock = { engaged:false, created_utc:\"\", genre:\"\", bpm:\"\", key_signature:\"\" };\n      setContinuityLockPortalFromMeta(tab);\n\n      // UPDATE #3: ensure SHA256_ID reset present\n      if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n\n      persistTabs();\n      renderPortals();\n      showToast(\"Editor cleared (all music portals reset).\", \"CLEAR\");\n    }\n\n    // =========================\n    // Receipt toggle\n    // =========================\n    function setReceiptOpen(open) {\n      state.receiptOpen = !!open;\n      storageSet(\"mh8_music_receipt_open\", state.receiptOpen ? \"1\" : \"0\");\n      receiptToggleBtn.setAttribute(\"aria-expanded\", state.receiptOpen ? \"true\" : \"false\");\n      receiptChev.textContent = state.receiptOpen ? \"▾\" : \"▸\";\n      receiptBody.className = state.receiptOpen ? \"cardBody\" : \"cardBody hidden\";\n    }\n    function toggleReceiptOpen() {\n      setReceiptOpen(!state.receiptOpen);\n      showToast(state.receiptOpen ? \"Receipt opened.\" : \"Receipt closed.\", \"RECEIPT\");\n    }\n\n    // =========================\n    // Load hooks\n    // =========================\n    function loadHooks() {\n      var rawImports = storageGet(\"mh8_music_imported_json_files_v1\", \"\");\n      if (rawImports) {\n        try {\n          var arr = JSON.parse(rawImports);\n          if (Object.prototype.toString.call(arr) === \"[object Array]\") {\n            state.imports = arr;\n            rebuildMergedHooksFromImports();\n            return;\n          }\n        } catch (e) {}\n      }\n\n      var raw = storageGet(\"mh8_music_imported_hooks\", \"\");\n      if (!raw) return;\n      try {\n        var arr2 = JSON.parse(raw);\n        if (Object.prototype.toString.call(arr2) === \"[object Array]\") state.hooks = arr2;\n      } catch (e) {}\n    }\n\n    // =========================\n    // Init\n    // =========================\n    function init() {\n      var savedPlatform = storageGet(\"mh8_music_platform\", \"suno\");\n      if (savedPlatform !== \"suno\" && savedPlatform !== \"udio\" && savedPlatform !== \"other\") savedPlatform = \"suno\";\n      state.platform = savedPlatform;\n      platformSelect.value = state.platform;\n\n      var savedTheme = storageGet(\"mh8_music_theme\", \"dark\");\n      if (savedTheme !== \"dark\" && savedTheme !== \"light\") savedTheme = \"dark\";\n      applyTheme(savedTheme);\n\n      var savedMode = storageGet(\"mh8_music_portal_mode\", \"quick\");\n      if (savedMode !== \"quick\" && savedMode !== \"semi\" && savedMode !== \"adv\") savedMode = \"quick\";\n      state.portal_mode = savedMode;\n\n      var ro = storageGet(\"mh8_music_receipt_open\", \"1\") === \"1\";\n      setReceiptOpen(ro);\n\n      loadTabs();\n      renderTabs();\n\n      loadHooks();\n\n      // UPDATE: initialize Portal Editor pack selector to MERGED\n      state.modal_hook_pack_mode = \"merged\";\n      state.modal_hook_pack_id = \"\";\n      renderHookPackMenu();\n\n      state.activePortalKey = \"MODEL_BEHAVIOR_CONTROL\";\n      renderModeButtons();\n      normalizeActivePortalForMode();\n\n      // UPDATE #2/#3: ensure continuity + SHA256_ID exist and portals are in sync on startup\n      var tab = findTab(state.activeId);\n      if (tab) {\n        getTabContinuity(tab);\n        setContinuityLockPortalFromMeta(tab);\n        if (tab.portals && typeof tab.portals.SHA256_ID === \"undefined\") tab.portals.SHA256_ID = \"\";\n        applyContinuityLockToPortals(tab);\n      }\n\n      renderPortals();\n\n      var savedReceipt = storageGet(\"mh8_music_receipt\", \"\");\n      if (savedReceipt) setReceiptText(savedReceipt);\n\n      var savedPE = storageGet(\"mh8_music_prompt_entries\", \"\");\n      if (savedPE) setPromptEntriesText(savedPE);\n      else renderPromptEntries();\n\n      var show = storageGet(\"mh8_music_show_log\", \"0\") === \"1\";\n      setLogVisible(show);\n\n      showToast(\"Ready. Build lyrics + style, stack hooks, mint proof receipts.\", \"MH8\");\n    }\n\n    // =========================\n    // Events\n    // =========================\n    platformSelect.onchange = function () {\n      state.platform = platformSelect.value;\n      storageSet(\"mh8_music_platform\", state.platform);\n      showToast(\"Platform set to \" + (state.platform === \"suno\" ? \"Suno\" : state.platform === \"udio\" ? \"Udio\" : \"Custom\") + \".\", \"PLATFORM\");\n    };\n\n    themeBtn.onclick = function () { toggleTheme(); };\n    logBtn.onclick = function () { setLogVisible(!state.showLog); };\n    addTabBtn.onclick = function () { addTab(); };\n\n    importBtn.onclick = function () { try { importFile.click(); } catch (e) { showToast(\"Import blocked by browser.\", \"IMPORT\"); } };\n    importMgrBtn.onclick = function () { openImportsManager(); };\n\n    importFile.onchange = function () {\n      var files = importFile.files;\n      if (!files || !files.length) return;\n      importFromFile(files[0]);\n      try { importFile.value = \"\"; } catch (e) {}\n    };\n\n    importsMergeBtn.onclick = function(){ var merged = rebuildMergedHooksFromImports(); showToast(\"Merged hooks rebuilt: \" + merged + \".\", \"IMPORTS\"); };\n    importsExportAllBtn.onclick = function(){ exportAllImportsAsStringArray(); };\n    importsClearAllBtn.onclick = function(){ clearAllImports(); };\n\n    modeQuickBtn.onclick = function(){ setPortalMode(\"quick\", true); };\n    modeSemiBtn.onclick = function(){ setPortalMode(\"semi\", true); };\n    modeAdvBtn.onclick = function(){ setPortalMode(\"adv\", true); };\n\n    portalPrevBtn.onclick = function(){ goPrevPortal(); };\n    portalNextBtn.onclick = function(){ goNextPortal(); };\n    portalJump.onchange = function(){ var idx = Number(portalJump.value); setActivePortalByIndex(idx, true); };\n\n    // Keyboard arrows for portal flow (when not typing / not in modal)\n    document.addEventListener(\"keydown\", function(e){\n      e = e || window.event;\n      var k = e.key || e.keyCode;\n\n      var tag = (document.activeElement && document.activeElement.tagName) ? String(document.activeElement.tagName).toLowerCase() : \"\";\n      var isTyping = (tag === \"textarea\" || tag === \"input\" || tag === \"select\");\n\n      if (modalBg.style.display === \"flex\") return;\n      if (importsBg.style.display === \"flex\") return;\n      if (promptEntriesBg.style.display === \"flex\") return;\n      if (isTyping) return;\n\n      if (k === \"ArrowLeft\" || k === 37) { prevent(e); goPrevPortal(); }\n      else if (k === \"ArrowRight\" || k === 39) { prevent(e); goNextPortal(); }\n      else if (k === \"Enter\" || k === 13) { prevent(e); if (state.activePortalKey) openPortalModal(state.activePortalKey); }\n    });\n\n    mintBtn.onclick = function () { mintReceipt(); };\n    copyBtn.onclick = function () { copyReceipt(); };\n    exportBtn.onclick = function () { exportReceipt(); };\n\n    clearReceiptBtn.onclick = function () { setReceiptText(\"\"); showToast(\"Receipt cleared (screen only).\", \"RECEIPT\"); };\n    clearEditorBtn.onclick = function () { clearEditor(); };\n\n    refreshLogBtn.onclick = function () { renderVault(); showToast(\"Log refreshed.\", \"LOG\"); };\n    exportVaultBtn.onclick = function () { exportVault(); };\n    clearVaultBtn.onclick = function () {\n      var ok = storageRemove(\"mh8_music_vault\");\n      renderVault();\n      showToast(ok ? \"Local vault cleared.\" : \"Vault clear blocked by device policy.\", \"LOG\");\n    };\n\n    receiptToggleBtn.onclick = function () { toggleReceiptOpen(); };\n\n    promptEntriesBtn.onclick = function (e) { if (e && e.stopPropagation) e.stopPropagation(); openPromptEntries(); };\n    promptEntriesCopyBtn.onclick = function(){ copyPromptEntries(); };\n    promptEntriesExportBtn.onclick = function(){ exportPromptEntries(); };\n    promptEntriesClearBtn.onclick = function(){ clearPromptEntries(); };\n\n    // Boot\n    init();\n\n  })();\n  </script>\n</body>\n</html>"
      }
    },
    "hash_input": "ACBEATZ.COM|{\"artifact\":{\"core_entry\":\"[MH8-Music Prompt Pro User Interface sha256 Sealed Code]\\n[https://zenodo.org/uploads/18665540][https://zenodo.org/records/18131984 (C T K L T) Core:\\nhttps://github.com/acbeatz\\nhttps://acbeatz.com/n-eyes\\nhttps://orcid.org/0009-0003-3846-9082]\\n\\n<!DOCTYPE html>  \\n<html lang=\\\"en\\\">\\n<head>\\n  <!--\\n    MH8 AI Music Proof-of-Creation Vault (Vanilla HTML5 SPA)\\n    Version: 2.0.0\\n    © 2026 ACBEATZ.COM\\n    Commercial SaaS Ready | Legacy + Mobile Hardened\\n\\n    Shell: PROMPT UI FORMAT (canonical)\\n    Converted from: MH8 AI Music Proof-of-Creation UI (style + lyrics + hooks + triple-layer receipt)\\n\\n    Core behavior preserved from PROMPT UI FORMAT:\\n    - Single-file SPA, no dependencies\\n    - Topbar: Platform select, Import Hooks JSON + Imports Manager, theme toggle, log toggle\\n    - Prompt tabs w/ per-tab delete\\n    - Entry Portals: tiered carousel (Quick/Semi/Advanced) with Prev/Next/dots/jump + keyboard arrows + modal editor\\n    - Hooks import: bracketed string extraction; select/append; manual typing supported\\n    - Imports Manager: list/merge/rebuild/delete/clear/export string-array of all imports\\n    - Three-layer receipt minting + collapsible receipt header toggle + ARIA expanded\\n    - PROMPT ENTRIES COMPLETED module: shows Layer 1+2 only with Copy/Clear/Export\\n    - Local vault log: per-entry copy + export/clear\\n    - Clipboard API + legacy fallback, toast + ARIA live, SHA-256 via crypto.subtle + fallback\\n    - Inline SVG \\\"8\\\" favicon\\n\\n    Conversion changes for AI Music:\\n    - Portal schema renamed for music prompts\\n    - Receipt layers redefined:\\n      Layer 1 = LYRICS (PASTE)\\n      Layer 2 = STYLE / TAGS / SETTINGS (PASTE)\\n      Layer 3 = HASHED RECEIPT (DO NOT PASTE)\\n    - All headers/titles/labels updated to MH8 AI Music naming\\n\\n    UPDATE (Additive only):\\n    - Portal Editor module sub-header menu listing ALL imported JSON hook packs (clickable)\\n    - Allows selecting a specific pack OR MERGED (all) directly inside Portal Editor\\n    - Hook list in Portal Editor filters to the selected pack; Append works exactly the same\\n\\n    UPDATE #1 (Additive only):\\n    - Retitle UI header/title to: \\\"MH8-Music-Prompt-Pro\\\" (Ai Music-Creators-Console)\\n    - All prior logic/features/functions remain verbatim.\\n\\n    UPDATE #2 (Additive only) — CONTINUITY LOCK FIX:\\n    - CONTINUITY LOCK portal now supports: DISENGAGED / ENGAGED + ADD (snapshot) behavior\\n    - When ENGAGED: locks GENRE + BPM + KEY_SIGNATURE (read-only in modal + updates blocked)\\n    - Continuity lock content is included in Layer 2 + full receipt payload (portals + layers)\\n    - Lock snapshot is deterministic and stored per-tab (does not affect other prompts)\\n\\n    UPDATE #3 (Additive only) — SHA256 ID PORTAL:\\n    - Add a final portal to the END of each portals menu section (Quick/Semi/Advanced) titled \\\"SHA256 ID\\\"\\n    - This portal holds the user’s SHA256 tracking # (production tracking ID)\\n    - SHA256 ID portal entry is included in Layer 2 + full receipt payload\\n    - Minted SHA256 (Layer 3 hash) remains present after minting as proof\\n    - END OF UPDATES\\n\\n    UPDATE #4 (Additive only) — CONTINUITY LOCK RECOGNIZES USER PORTAL INPUTS:\\n    - When ENGAGE is pressed (or when continuity is engaged programmatically), the lock now captures the CURRENT\\n      portal values for GENRE, BPM, KEY_SIGNATURE and stores them into the continuity lock snapshot before enforcing.\\n    - This ensures receipts show non-blank values like:\\n      [CONTINUITY_LOCK] ENGAGED | LOCKED_UTC: ... | GENRE: X | BPM: Y | KEY_SIGNATURE: Z\\n    - All other behavior remains unchanged.\\n\\n    UPDATE #5 (Additive only) — PROMPT ENTRIES COMPLETED MUST INCLUDE MINTED SHA256:\\n    - When user clicks \\\"Mint Proof Receipt\\\", the minted SHA256 hash for that receipt is included in the\\n      PROMPT ENTRIES COMPLETED output as the LAST entry:\\n      [SHA 256 ID + hex64]\\n    - Every minted receipt updates PROMPT ENTRIES COMPLETED with its unique SHA256 appended as final line.\\n  -->\\n  <meta charset=\\\"UTF-8\\\" />\\n  <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1, viewport-fit=cover\\\" />\\n  <meta http-equiv=\\\"X-UA-Compatible\\\" content=\\\"IE=edge\\\" />\\n  <title>MH8-Music-Prompt-Pro (Ai Music-Creators-Console)</title>\\n\\n  <!-- SVG \\\"8\\\" favicon -->\\n  <link rel=\\\"icon\\\" type=\\\"image/svg+xml\\\"\\n    href='data:image/svg+xml;utf8,<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" viewBox=\\\"0 0 64 64\\\"><rect width=\\\"64\\\" height=\\\"64\\\" rx=\\\"14\\\" fill=\\\"%2306070b\\\"/><path d=\\\"M32 12c-7 0-12 4.6-12 11 0 4.1 2.2 7.4 5.7 9.2C21.4 34 18 37.8 18 43c0 6.8 6 11 14 11s14-4.2 14-11c0-5.2-3.4-9-7.7-10.8C41.8 30.4 44 27.1 44 23c0-6.4-5-11-12-11zm0 6c3.7 0 6 2 6 5 0 3.1-2.5 5-6 5s-6-1.9-6-5c0-3 2.3-5 6-5zm0 16c4.7 0 8 2.7 8 6.5S36.7 47 32 47s-8-2.7-8-6.5S27.3 34 32 34z\\\" fill=\\\"%234fd1c5\\\"/></svg>' />\\n\\n  <style>\\n    :root{\\n      --bg:#06070b;--panel:#0d1224;--panel2:#0b1020;--text:#e9ecf5;--muted:#a7b0c3;\\n      --line:rgba(255,255,255,.10);--accent:#4fd1c5;--accent2:#7c3aed;--danger:#f56565;\\n      --shadow:0 12px 40px rgba(0,0,0,.35);\\n      --radius:14px;\\n      --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \\\"Liberation Mono\\\", \\\"Courier New\\\", monospace;\\n      --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, \\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\";\\n      --orange:#f59e0b;\\n    }\\n    html[data-theme=\\\"light\\\"]{\\n      --bg:#f6f7fb;--panel:#ffffff;--panel2:#ffffff;--text:#0b1220;--muted:#4b5565;\\n      --line:rgba(15,23,42,.12);--shadow:0 16px 50px rgba(2,6,23,.10);\\n    }\\n    *{box-sizing:border-box}\\n    body{margin:0;font-family:var(--sans);background:var(--bg);color:var(--text);min-height:100vh;}\\n    .wrap{max-width:1200px;margin:0 auto;padding:14px}\\n    .topbar{\\n      display:flex;align-items:center;justify-content:space-between;gap:12px;\\n      padding:12px 12px;border:1px solid var(--line);border-radius:var(--radius);\\n      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));\\n      box-shadow:var(--shadow);\\n    }\\n    html[data-theme=\\\"light\\\"] .topbar{background:linear-gradient(180deg, rgba(2,6,23,.03), rgba(2,6,23,.01))}\\n    .brand{display:flex;flex-direction:column;gap:2px}\\n    .brand h1{margin:0;font-size:16px;letter-spacing:.2px}\\n    .brand .sub{font-size:12px;color:var(--muted)}\\n    .controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end}\\n    .select, .btn{\\n      font-size:13px;border-radius:12px;border:1px solid var(--line);\\n      background:rgba(255,255,255,.03);color:var(--text);\\n      padding:9px 10px;outline:none;\\n    }\\n    html[data-theme=\\\"light\\\"] .select, html[data-theme=\\\"light\\\"] .btn{background:rgba(2,6,23,.03)}\\n    .btn{cursor:pointer;user-select:none}\\n    .btn:focus{box-shadow:0 0 0 3px rgba(79,209,197,.22)}\\n    .btn.primary{border-color:rgba(79,209,197,.45); background:rgba(79,209,197,.10)}\\n    .btn.secondary{border-color:rgba(124,58,237,.35); background:rgba(124,58,237,.10)}\\n    .btn.danger{border-color:rgba(245,101,101,.35); background:rgba(245,101,101,.10)}\\n    .btn.small{padding:7px 9px;border-radius:10px;font-size:12px}\\n    .btn.icon{padding:7px 9px;border-radius:10px;font-size:12px;display:inline-flex;gap:6px;align-items:center}\\n    .btn[disabled]{opacity:.55;cursor:not-allowed}\\n\\n    .tabsBar{margin-top:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;}\\n    .tablist{\\n      display:flex;gap:8px;align-items:center;flex-wrap:wrap;\\n      padding:10px;border:1px solid var(--line);border-radius:var(--radius);\\n      background:rgba(255,255,255,.02);\\n    }\\n    html[data-theme=\\\"light\\\"] .tablist{background:rgba(2,6,23,.02)}\\n    .tabWrap{display:flex;align-items:center;gap:6px}\\n    .tab{\\n      border:1px solid var(--line);background:rgba(255,255,255,.03);color:var(--text);\\n      padding:8px 10px;border-radius:12px;cursor:pointer;font-size:13px;\\n    }\\n    html[data-theme=\\\"light\\\"] .tab{background:rgba(2,6,23,.03)}\\n    .tab[aria-selected=\\\"true\\\"]{border-color:rgba(79,209,197,.55);background:rgba(79,209,197,.12);}\\n    .tab:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.22)}\\n    .tabDel{\\n      border:1px solid var(--line);background:rgba(255,255,255,.02);color:var(--muted);\\n      padding:8px 9px;border-radius:12px;cursor:pointer;font-size:12px;\\n    }\\n    html[data-theme=\\\"light\\\"] .tabDel{background:rgba(2,6,23,.02)}\\n    .tabDel:hover{color:var(--text);border-color:rgba(245,101,101,.35);background:rgba(245,101,101,.10)}\\n\\n    .grid{margin-top:12px;display:grid;grid-template-columns:1fr;gap:12px;}\\n    .footerZone{margin-top:12px;}\\n    .footerZone .card{width:100%;}\\n\\n    .card{\\n      border:1px solid var(--line);border-radius:var(--radius);\\n      background:rgba(255,255,255,.02);box-shadow:var(--shadow);overflow:hidden;\\n    }\\n    html[data-theme=\\\"light\\\"] .card{background:rgba(2,6,23,.02)}\\n    .cardHeader{\\n      display:flex;align-items:center;justify-content:space-between;gap:10px;\\n      padding:12px 12px;border-bottom:1px solid var(--line);\\n    }\\n    .cardHeader h2{margin:0;font-size:13px;letter-spacing:.2px}\\n    .cardHeader .hint{color:var(--muted);font-size:12px}\\n    .cardBody{padding:12px}\\n    .actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}\\n    .headerOrange{color:var(--orange);}\\n\\n    .copyrightFooter{\\n      margin-top:10px;\\n      padding:10px 12px;\\n      border:1px solid var(--line);\\n      border-radius:var(--radius);\\n      background:rgba(255,255,255,.02);\\n      color:var(--muted);\\n      font-size:12px;\\n      text-align:center;\\n    }\\n    html[data-theme=\\\"light\\\"] .copyrightFooter{background:rgba(2,6,23,.02)}\\n\\n    .portal{\\n      border:1px solid var(--line);\\n      border-radius:14px;\\n      background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));\\n      padding:10px;\\n      cursor:pointer;\\n      transition:transform .12s ease, border-color .12s ease, background .12s ease;\\n      min-height:120px;\\n      position:relative;\\n      min-width:0;\\n    }\\n    html[data-theme=\\\"light\\\"] .portal{background:linear-gradient(180deg, rgba(2,6,23,.03), rgba(2,6,23,.01))}\\n    .portal:hover{transform:translateY(-1px)}\\n    .portal.active{\\n      border-color:rgba(79,209,197,.55);\\n      box-shadow:0 0 0 3px rgba(79,209,197,.12) inset;\\n    }\\n    .portal.locked{\\n      border-color:rgba(245,158,11,.55);\\n      box-shadow:0 0 0 3px rgba(245,158,11,.12) inset;\\n    }\\n    .pHead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px}\\n    .pTitle{font-size:12px;letter-spacing:.25px;color:var(--orange);}\\n    .pBadge{\\n      font-size:11px;color:var(--muted);\\n      border:1px solid var(--line);border-radius:999px;padding:3px 8px;\\n      background:rgba(255,255,255,.03);\\n      white-space:nowrap;\\n    }\\n    html[data-theme=\\\"light\\\"] .pBadge{background:rgba(2,6,23,.03)}\\n    .pValue{\\n      border:1px solid var(--line);\\n      border-radius:12px;\\n      padding:10px;\\n      background:rgba(255,255,255,.02);\\n      font-family:var(--mono);\\n      font-size:12px;\\n      color:var(--text);\\n      white-space:pre-wrap;\\n      word-break:break-word;\\n      min-height:60px;\\n      min-width:0;\\n    }\\n    html[data-theme=\\\"light\\\"] .pValue{background:rgba(2,6,23,.02)}\\n    .pEmpty{color:var(--muted)}\\n    .portalNote{margin-top:10px;color:var(--muted);font-size:12px}\\n\\n    .carouselWrap{\\n      border:1px solid var(--line);\\n      border-radius:16px;\\n      background:rgba(255,255,255,.02);\\n      padding:10px;\\n      box-shadow:0 10px 28px rgba(0,0,0,.18);\\n    }\\n    html[data-theme=\\\"light\\\"] .carouselWrap{background:rgba(2,6,23,.02)}\\n    .carouselTop{\\n      display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;\\n      margin-bottom:10px;\\n    }\\n    .carouselLeft{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\\n    .carouselRight{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}\\n    .carouselCounter{\\n      font-size:12px;color:var(--muted);\\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\\n      background:rgba(255,255,255,.03);\\n      font-family:var(--mono);\\n    }\\n    html[data-theme=\\\"light\\\"] .carouselCounter{background:rgba(2,6,23,.03)}\\n    .carouselDots{\\n      display:flex;gap:6px;flex-wrap:wrap;align-items:center;\\n      max-width:100%;\\n    }\\n    .dot{\\n      width:9px;height:9px;border-radius:999px;\\n      border:1px solid var(--line);\\n      background:rgba(255,255,255,.02);\\n      cursor:pointer;\\n    }\\n    html[data-theme=\\\"light\\\"] .dot{background:rgba(2,6,23,.02)}\\n    .dot.active{\\n      border-color:rgba(79,209,197,.65);\\n      background:rgba(79,209,197,.25);\\n      box-shadow:0 0 0 2px rgba(79,209,197,.10);\\n    }\\n    .carouselStage{\\n      display:grid;\\n      grid-template-columns:1fr;\\n      gap:10px;\\n      min-width:0;\\n    }\\n    .carouselHintRow{\\n      margin-top:8px;\\n      display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;\\n      color:var(--muted);font-size:12px;\\n    }\\n    .jumpSelect{\\n      font-size:12px;\\n      border-radius:12px;\\n      border:1px solid var(--line);\\n      background:rgba(255,255,255,.03);\\n      color:var(--text);\\n      padding:8px 10px;\\n      outline:none;\\n      max-width:100%;\\n    }\\n    html[data-theme=\\\"light\\\"] .jumpSelect{background:rgba(2,6,23,.03)}\\n    .kbdHint{\\n      font-family:var(--mono);\\n      font-size:11px;\\n      border:1px dashed var(--line);\\n      border-radius:10px;\\n      padding:4px 8px;\\n      background:rgba(255,255,255,.02);\\n    }\\n    html[data-theme=\\\"light\\\"] .kbdHint{background:rgba(2,6,23,.02)}\\n\\n    .modeBar{\\n      display:flex;gap:6px;flex-wrap:wrap;align-items:center;\\n      border:1px solid var(--line);\\n      border-radius:999px;\\n      padding:4px;\\n      background:rgba(255,255,255,.02);\\n    }\\n    html[data-theme=\\\"light\\\"] .modeBar{background:rgba(2,6,23,.02)}\\n    .modeBtn{\\n      border:1px solid transparent;\\n      background:transparent;\\n      color:var(--muted);\\n      padding:6px 10px;\\n      border-radius:999px;\\n      cursor:pointer;\\n      font-size:12px;\\n      line-height:1;\\n      user-select:none;\\n      white-space:nowrap;\\n    }\\n    .modeBtn:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.18)}\\n    .modeBtn.active{\\n      color:var(--text);\\n      border-color:rgba(79,209,197,.45);\\n      background:rgba(79,209,197,.10);\\n    }\\n    .modeTag{\\n      font-size:11px;color:var(--muted);\\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\\n      background:rgba(255,255,255,.03);\\n      font-family:var(--mono);\\n    }\\n    html[data-theme=\\\"light\\\"] .modeTag{background:rgba(2,6,23,.03)}\\n\\n    .modalBg{\\n      position:fixed;inset:0;background:rgba(0,0,0,.6);\\n      display:none;align-items:center;justify-content:center;\\n      z-index:9998;padding:14px;\\n    }\\n    .modal{\\n      width:100%;\\n      max-width:980px;\\n      border:1px solid var(--line);\\n      border-radius:16px;\\n      background:var(--panel);\\n      box-shadow:0 30px 90px rgba(0,0,0,.5);\\n      overflow:hidden;\\n    }\\n    html[data-theme=\\\"light\\\"] .modal{background:#fff}\\n    .modalTop{\\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\\n      padding:12px;border-bottom:1px solid var(--line);\\n    }\\n    .modalTop .t{font-size:13px}\\n    .modalTop .s{font-size:12px;color:var(--muted)}\\n\\n    /* UPDATE: Portal Editor sub-header pack menu (additive) */\\n    .packBar{\\n      margin-top:8px;\\n      display:flex;\\n      gap:8px;\\n      align-items:center;\\n      flex-wrap:wrap;\\n    }\\n    .packLabel{\\n      font-size:12px;\\n      color:var(--muted);\\n      white-space:nowrap;\\n    }\\n    .packChips{\\n      display:flex;\\n      gap:6px;\\n      align-items:center;\\n      overflow:auto;\\n      max-width:100%;\\n      padding:4px;\\n      border:1px solid var(--line);\\n      border-radius:999px;\\n      background:rgba(255,255,255,.02);\\n      -webkit-overflow-scrolling:touch;\\n    }\\n    html[data-theme=\\\"light\\\"] .packChips{background:rgba(2,6,23,.02)}\\n    .packChip{\\n      border:1px solid transparent;\\n      background:transparent;\\n      color:var(--muted);\\n      padding:6px 10px;\\n      border-radius:999px;\\n      cursor:pointer;\\n      font-size:12px;\\n      line-height:1;\\n      user-select:none;\\n      white-space:nowrap;\\n    }\\n    .packChip:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.18)}\\n    .packChip.active{\\n      color:var(--text);\\n      border-color:rgba(79,209,197,.45);\\n      background:rgba(79,209,197,.10);\\n    }\\n    .packActivePill{\\n      font-size:11px;color:var(--muted);\\n      border:1px solid var(--line);\\n      border-radius:999px;\\n      padding:4px 8px;\\n      background:rgba(255,255,255,.03);\\n      font-family:var(--mono);\\n      white-space:nowrap;\\n    }\\n    html[data-theme=\\\"light\\\"] .packActivePill{background:rgba(2,6,23,.03)}\\n\\n    /* UPDATE #2: Continuity lock bar in Portal Editor (additive) */\\n    .lockBar{\\n      margin-top:8px;\\n      display:none;\\n      gap:8px;\\n      align-items:center;\\n      flex-wrap:wrap;\\n    }\\n    .lockPill{\\n      font-size:11px;color:var(--muted);\\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\\n      background:rgba(255,255,255,.03);\\n      font-family:var(--mono);\\n      white-space:nowrap;\\n    }\\n    html[data-theme=\\\"light\\\"] .lockPill{background:rgba(2,6,23,.03)}\\n    .lockPill.engaged{\\n      border-color:rgba(245,158,11,.55);\\n      background:rgba(245,158,11,.10);\\n      color:var(--text);\\n    }\\n\\n    .modalBody{padding:12px;display:grid;grid-template-columns:1fr;gap:12px}\\n    @media(min-width:980px){\\n      .modalBody{grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr)}\\n    }\\n    .fieldLabel{font-size:12px;color:var(--muted);margin:0 0 6px 2px}\\n    .modal textarea{\\n      width:100%;min-height:220px;resize:vertical;\\n      border:1px solid var(--line);border-radius:12px;padding:10px;\\n      background:rgba(255,255,255,.03);color:var(--text);\\n      font-family:var(--mono);font-size:12px;line-height:1.35;outline:none;\\n    }\\n    html[data-theme=\\\"light\\\"] .modal textarea{background:rgba(2,6,23,.03)}\\n    .modal textarea[readonly]{\\n      opacity:.9;\\n      border-color:rgba(245,158,11,.45);\\n      background:rgba(245,158,11,.05);\\n      cursor:not-allowed;\\n    }\\n\\n    .hookList{\\n      border:1px solid var(--line);\\n      border-radius:12px;\\n      padding:10px;\\n      background:rgba(255,255,255,.02);\\n      max-height:260px;\\n      overflow:auto;\\n      min-width:0;\\n    }\\n    html[data-theme=\\\"light\\\"] .hookList{background:rgba(2,6,23,.02)}\\n    .hookItem{\\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\\n      padding:8px;border:1px solid var(--line);border-radius:12px;\\n      background:rgba(255,255,255,.02);\\n      margin-bottom:8px;\\n      min-width:0;\\n    }\\n    html[data-theme=\\\"light\\\"] .hookItem{background:rgba(2,6,23,.02)}\\n    .hookItem:last-child{margin-bottom:0}\\n    .hookText{font-family:var(--mono);font-size:12px;white-space:pre-wrap;word-break:break-word;min-width:0}\\n    .hookBtns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}\\n\\n    pre{\\n      margin:0;\\n      white-space:pre-wrap;\\n      word-break:break-word;\\n      border:1px solid var(--line);\\n      border-radius:12px;\\n      padding:10px;\\n      background:rgba(255,255,255,.03);\\n      color:var(--text);\\n      font-family:var(--mono);\\n      font-size:12px;\\n      line-height:1.35;\\n      max-height:320px;\\n      overflow:auto;\\n      min-width:0;\\n    }\\n    .hidden{display:none !important}\\n\\n    .logCompact .cardBody{padding:12px}\\n    .logViewport{\\n      border:1px solid var(--line);\\n      border-radius:12px;\\n      background:rgba(255,255,255,.02);\\n      padding:10px;\\n      max-height:220px;\\n      overflow:auto;\\n    }\\n    html[data-theme=\\\"light\\\"] .logViewport{background:rgba(2,6,23,.02)}\\n    .logEntry{\\n      border:1px solid var(--line);\\n      border-radius:12px;\\n      padding:10px;\\n      margin-bottom:10px;\\n      background:rgba(255,255,255,.02);\\n    }\\n    html[data-theme=\\\"light\\\"] .logEntry{background:rgba(2,6,23,.02)}\\n    .logEntry:last-child{margin-bottom:0}\\n    .logMeta{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between;margin-bottom:8px}\\n    .logMeta .left{display:flex;gap:10px;flex-wrap:wrap;align-items:center}\\n    .pill{\\n      font-size:11px;color:var(--muted);\\n      border:1px solid var(--line);border-radius:999px;padding:4px 8px;\\n      background:rgba(255,255,255,.03);\\n      font-family:var(--mono);\\n    }\\n    html[data-theme=\\\"light\\\"] .pill{background:rgba(2,6,23,.03)}\\n    .pill.locked{\\n      border-color:rgba(245,158,11,.55);\\n      background:rgba(245,158,11,.10);\\n      color:var(--text);\\n    }\\n    .logActions{display:flex;gap:8px;flex-wrap:wrap}\\n    .logPre{\\n      white-space:pre-wrap;word-break:break-word;\\n      font-family:var(--mono);font-size:12px;line-height:1.35;\\n      margin:0;\\n    }\\n\\n    .toastWrap{\\n      position:fixed;left:12px; right:12px; bottom:12px;\\n      display:flex; justify-content:center;\\n      pointer-events:none;z-index:9999;\\n    }\\n    .toast{\\n      pointer-events:none;\\n      max-width:920px;width:100%;\\n      border:1px solid var(--line);\\n      border-radius:14px;\\n      padding:10px 12px;\\n      background:rgba(13,18,36,.92);\\n      color:var(--text);\\n      box-shadow:0 18px 60px rgba(0,0,0,.35);\\n      display:flex; align-items:flex-start; justify-content:space-between; gap:12px;\\n      transform:translateY(14px);\\n      opacity:0;\\n      transition:opacity .18s ease, transform .18s ease;\\n    }\\n    html[data-theme=\\\"light\\\"] .toast{background:rgba(255,255,255,.95)}\\n    .toast.show{opacity:1; transform:translateY(0)}\\n    .toast .msg{font-size:13px; line-height:1.3}\\n    .toast .tag{\\n      font-size:11px;color:var(--muted);white-space:nowrap;\\n      border:1px solid var(--line);border-radius:999px;\\n      padding:4px 8px;background:rgba(255,255,255,.03);\\n    }\\n    html[data-theme=\\\"light\\\"] .toast .tag{background:rgba(2,6,23,.03)}\\n\\n    .srOnly{\\n      position:absolute !important;\\n      width:1px;height:1px;\\n      padding:0;margin:-1px;\\n      overflow:hidden;clip:rect(0,0,0,0);\\n      white-space:nowrap;border:0;\\n    }\\n    #legacyCopyArea{position:absolute;left:-9999px;top:-9999px}\\n\\n    .importsSummary{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-top:10px;}\\n    .importsList{\\n      border:1px solid var(--line);\\n      border-radius:12px;\\n      padding:10px;\\n      background:rgba(255,255,255,.02);\\n      max-height:320px;\\n      overflow:auto;\\n      min-width:0;\\n    }\\n    html[data-theme=\\\"light\\\"] .importsList{background:rgba(2,6,23,.02)}\\n    .importRow{\\n      display:flex;align-items:flex-start;justify-content:space-between;gap:10px;\\n      padding:10px;border:1px solid var(--line);border-radius:12px;\\n      background:rgba(255,255,255,.02);\\n      margin-bottom:10px;\\n      min-width:0;\\n    }\\n    html[data-theme=\\\"light\\\"] .importRow{background:rgba(2,6,23,.02)}\\n    .importRow:last-child{margin-bottom:0}\\n    .importMeta{min-width:0}\\n    .importName{font-size:12px;color:var(--text);font-family:var(--mono);word-break:break-word}\\n    .importSub{font-size:12px;color:var(--muted);margin-top:4px}\\n    .importBtns{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}\\n    .miniNote{font-size:12px;color:var(--muted);line-height:1.35}\\n\\n    .receiptToggleBtn{\\n      appearance:none;border:0;background:transparent;padding:0;margin:0;\\n      color:var(--text);font-size:13px;letter-spacing:.2px;cursor:pointer;\\n      display:inline-flex;align-items:center;gap:8px;\\n    }\\n    .receiptToggleBtn:focus{outline:none;box-shadow:0 0 0 3px rgba(79,209,197,.22);border-radius:10px}\\n    .chev{display:inline-block;width:16px;text-align:center;color:var(--muted);font-family:var(--mono);}\\n    .receiptHeaderRight{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;}\\n  </style>\\n</head>\\n\\n<body>\\n  <div class=\\\"wrap\\\">\\n    <header class=\\\"topbar\\\" role=\\\"banner\\\">\\n      <div class=\\\"brand\\\">\\n        <h1>MH8-Music-Prompt-Pro (Ai Music-Creators-Console)</h1>\\n        <div class=\\\"sub\\\">Vanilla SPA • Music portals + hooks import + triple-layer proof receipts</div>\\n      </div>\\n\\n      <div class=\\\"controls\\\" aria-label=\\\"Top controls\\\">\\n        <label class=\\\"srOnly\\\" for=\\\"platformSelect\\\">Platform</label>\\n        <select id=\\\"platformSelect\\\" class=\\\"select\\\" aria-label=\\\"Select music platform\\\">\\n          <option value=\\\"suno\\\">Suno</option>\\n          <option value=\\\"udio\\\">Udio</option>\\n          <option value=\\\"other\\\">Other / Custom</option>\\n        </select>\\n\\n        <button id=\\\"importBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\" aria-haspopup=\\\"dialog\\\">Import Hooks JSON</button>\\n        <button id=\\\"importMgrBtn\\\" class=\\\"btn secondary icon small\\\" type=\\\"button\\\" aria-haspopup=\\\"dialog\\\" title=\\\"Manage imported JSON files\\\">☰ Imports</button>\\n        <input id=\\\"importFile\\\" type=\\\"file\\\" accept=\\\".json,application/json,text/plain\\\" class=\\\"srOnly\\\" />\\n\\n        <button id=\\\"themeBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\" aria-pressed=\\\"false\\\">Light Mode</button>\\n        <button id=\\\"logBtn\\\" class=\\\"btn\\\" type=\\\"button\\\" aria-expanded=\\\"false\\\" aria-controls=\\\"logCard\\\">Log</button>\\n      </div>\\n    </header>\\n\\n    <div class=\\\"tabsBar\\\">\\n      <div id=\\\"tablist\\\" class=\\\"tablist\\\" role=\\\"tablist\\\" aria-label=\\\"Prompt tabs\\\"></div>\\n      <button id=\\\"addTabBtn\\\" class=\\\"btn small primary\\\" type=\\\"button\\\" aria-label=\\\"Add new prompt tab\\\">+ Add Prompt</button>\\n    </div>\\n\\n    <main class=\\\"grid\\\" role=\\\"main\\\">\\n      <section class=\\\"card\\\" aria-label=\\\"Prompt editor\\\">\\n        <div class=\\\"cardHeader\\\">\\n          <h2 class=\\\"headerOrange\\\">Music Prompt Editor — Entry Portals</h2>\\n          <div class=\\\"hint\\\">Step portal-by-portal → stack hooks → Mint proof</div>\\n        </div>\\n\\n        <div class=\\\"cardBody\\\">\\n          <div class=\\\"carouselWrap\\\" aria-label=\\\"Entry portals carousel\\\">\\n            <div class=\\\"carouselTop\\\">\\n              <div class=\\\"carouselLeft\\\">\\n                <span class=\\\"pill\\\">Flow</span>\\n\\n                <div class=\\\"modeBar\\\" role=\\\"group\\\" aria-label=\\\"Portal tier modes\\\">\\n                  <button id=\\\"modeQuickBtn\\\" class=\\\"modeBtn\\\" type=\\\"button\\\" aria-pressed=\\\"false\\\">Quick Start</button>\\n                  <button id=\\\"modeSemiBtn\\\" class=\\\"modeBtn\\\" type=\\\"button\\\" aria-pressed=\\\"false\\\">Semi Pro</button>\\n                  <button id=\\\"modeAdvBtn\\\" class=\\\"modeBtn\\\" type=\\\"button\\\" aria-pressed=\\\"false\\\">Advanced</button>\\n                </div>\\n                <span id=\\\"modeLabelPill\\\" class=\\\"modeTag\\\">mode: QUICK</span>\\n\\n                <span id=\\\"portalCounter\\\" class=\\\"carouselCounter\\\">0/0</span>\\n                <span class=\\\"kbdHint\\\">← →</span>\\n                <span class=\\\"kbdHint\\\">Enter</span>\\n              </div>\\n              <div class=\\\"carouselRight\\\">\\n                <label class=\\\"srOnly\\\" for=\\\"portalJump\\\">Jump to portal</label>\\n                <select id=\\\"portalJump\\\" class=\\\"jumpSelect\\\" aria-label=\\\"Jump to portal\\\"></select>\\n                <button id=\\\"portalPrevBtn\\\" class=\\\"btn small\\\" type=\\\"button\\\" aria-label=\\\"Previous portal\\\">◀ Prev</button>\\n                <button id=\\\"portalNextBtn\\\" class=\\\"btn small primary\\\" type=\\\"button\\\" aria-label=\\\"Next portal\\\">Next ▶</button>\\n              </div>\\n            </div>\\n\\n            <div id=\\\"portalStage\\\" class=\\\"carouselStage\\\" aria-label=\\\"Active portal stage\\\"></div>\\n\\n            <div class=\\\"carouselHintRow\\\">\\n              <div id=\\\"portalDots\\\" class=\\\"carouselDots\\\" aria-label=\\\"Portal progress dots\\\"></div>\\n              <div style=\\\"display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:flex-end\\\">\\n                <span style=\\\"color:var(--muted);font-size:12px\\\">Tip: Click portal card to edit. Hooks append in the modal.</span>\\n              </div>\\n            </div>\\n          </div>\\n\\n          <div class=\\\"portalNote\\\">\\n            Active portal: <span id=\\\"activePortalName\\\" class=\\\"pill\\\">None</span>\\n            <span id=\\\"continuityStatusPill\\\" class=\\\"pill\\\" style=\\\"margin-left:8px\\\">continuity: DISENGAGED</span>\\n            <span style=\\\"margin-left:8px;color:var(--muted);font-size:12px\\\">Tip: Import hook packs, then inject hooks into any portal.</span>\\n          </div>\\n\\n          <div class=\\\"actions\\\" role=\\\"group\\\" aria-label=\\\"Editor actions\\\">\\n            <button id=\\\"mintBtn\\\" class=\\\"btn primary\\\" type=\\\"button\\\">Mint Proof Receipt</button>\\n            <button id=\\\"copyBtn\\\" class=\\\"btn\\\" type=\\\"button\\\">Copy Receipt</button>\\n            <button id=\\\"exportBtn\\\" class=\\\"btn\\\" type=\\\"button\\\">Export Receipt</button>\\n            <button id=\\\"clearEditorBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\" title=\\\"Clears ALL portal entries in the current prompt\\\">Clear Editor</button>\\n            <button id=\\\"clearReceiptBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\" title=\\\"Clears only the current on-screen receipt (does not delete vault log)\\\">Clear Receipt</button>\\n          </div>\\n\\n          <div class=\\\"srOnly\\\" id=\\\"ariaStatus\\\" role=\\\"status\\\" aria-live=\\\"polite\\\" aria-atomic=\\\"true\\\"></div>\\n        </div>\\n      </section>\\n\\n      <section class=\\\"card\\\" aria-label=\\\"Receipt viewer\\\">\\n        <div class=\\\"cardHeader\\\" id=\\\"receiptHeader\\\">\\n          <button id=\\\"receiptToggleBtn\\\" class=\\\"receiptToggleBtn\\\" type=\\\"button\\\" aria-expanded=\\\"true\\\" aria-controls=\\\"receiptBody\\\">\\n            <span class=\\\"chev\\\" id=\\\"receiptChev\\\">▾</span>\\n            <span>Three-Layer Music Receipt</span>\\n          </button>\\n\\n          <div class=\\\"receiptHeaderRight\\\">\\n            <button id=\\\"promptEntriesBtn\\\" class=\\\"btn secondary small\\\" type=\\\"button\\\" aria-haspopup=\\\"dialog\\\" title=\\\"Shows Layer 1 + Layer 2 only (ready to paste)\\\">\\n              PROMPT ENTRIES COMPLETED\\n            </button>\\n            <div class=\\\"hint\\\">Paste Layer 1 + 2 into Suno/Udio • Keep Layer 3 as proof</div>\\n          </div>\\n        </div>\\n\\n        <div class=\\\"cardBody\\\" id=\\\"receiptBody\\\">\\n          <pre id=\\\"receiptPre\\\" aria-label=\\\"Minted receipt output\\\">No receipt minted yet.</pre>\\n          <textarea id=\\\"legacyCopyArea\\\" aria-hidden=\\\"true\\\"></textarea>\\n        </div>\\n      </section>\\n    </main>\\n\\n    <footer class=\\\"footerZone\\\" role=\\\"contentinfo\\\">\\n      <section id=\\\"logCard\\\" class=\\\"card hidden logCompact\\\" aria-label=\\\"Minted receipt log\\\">\\n        <div class=\\\"cardHeader\\\">\\n          <h2>Minted Proof Log (Local)</h2>\\n          <div class=\\\"hint\\\">Compact • scroll • copy per entry</div>\\n        </div>\\n        <div class=\\\"cardBody\\\">\\n          <div class=\\\"actions\\\" role=\\\"group\\\" aria-label=\\\"Log actions\\\" style=\\\"margin-top:0;margin-bottom:10px\\\">\\n            <button id=\\\"refreshLogBtn\\\" class=\\\"btn\\\" type=\\\"button\\\">Refresh Log</button>\\n            <button id=\\\"exportVaultBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\">Export Vault JSON</button>\\n            <button id=\\\"clearVaultBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\" title=\\\"Deletes local vault log from this browser/device only\\\">Clear Vault (Local)</button>\\n          </div>\\n\\n          <div id=\\\"logViewport\\\" class=\\\"logViewport\\\" aria-label=\\\"Vault log viewport\\\">\\n            <div style=\\\"color:var(--muted);font-size:12px\\\">No receipts logged yet.</div>\\n          </div>\\n        </div>\\n      </section>\\n\\n      <div class=\\\"copyrightFooter\\\">Copyrights Acbeatz.com IP Property 2026</div>\\n    </footer>\\n  </div>\\n\\n  <!-- Portal + Hooks modal -->\\n  <div id=\\\"modalBg\\\" class=\\\"modalBg\\\" role=\\\"dialog\\\" aria-modal=\\\"true\\\" aria-labelledby=\\\"modalTitle\\\" aria-hidden=\\\"true\\\">\\n    <div class=\\\"modal\\\">\\n      <div class=\\\"modalTop\\\">\\n        <div style=\\\"min-width:0\\\">\\n          <div id=\\\"modalTitle\\\" class=\\\"t\\\">Portal Editor</div>\\n          <div id=\\\"modalSub\\\" class=\\\"s\\\">Click a hook to append, or type your custom entry.</div>\\n\\n          <!-- UPDATE: Sub-header menu inside Portal Editor to switch hook packs -->\\n          <div class=\\\"packBar\\\" aria-label=\\\"Hook pack selector inside Portal Editor\\\">\\n            <span class=\\\"packLabel\\\">Hook Pack:</span>\\n            <div id=\\\"modalPackChips\\\" class=\\\"packChips\\\" role=\\\"group\\\" aria-label=\\\"Imported JSON hook packs (click to select)\\\"></div>\\n            <span id=\\\"modalActivePackPill\\\" class=\\\"packActivePill\\\">active: MERGED</span>\\n          </div>\\n\\n          <!-- UPDATE #2: Continuity Lock controls (only shown on CONTINUITY_LOCK portal) -->\\n          <div id=\\\"lockBar\\\" class=\\\"lockBar\\\" aria-label=\\\"Continuity lock controls\\\">\\n            <span id=\\\"lockStatusPill\\\" class=\\\"lockPill\\\">DISENGAGED</span>\\n            <button id=\\\"lockAddBtn\\\" class=\\\"btn secondary small\\\" type=\\\"button\\\" title=\\\"Adds (snapshots) GENRE + BPM + KEY into Continuity Lock and engages lock\\\">ADD LOCK (GENRE+BPM+KEY)</button>\\n            <button id=\\\"lockEngageBtn\\\" class=\\\"btn small primary\\\" type=\\\"button\\\" title=\\\"Engage continuity lock using current lock snapshot\\\">ENGAGE</button>\\n            <button id=\\\"lockDisengageBtn\\\" class=\\\"btn small danger\\\" type=\\\"button\\\" title=\\\"Disengage continuity lock (unlocks GENRE/BPM/KEY editing)\\\">DISENGAGE</button>\\n          </div>\\n        </div>\\n\\n        <div style=\\\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\\\">\\n          <button id=\\\"modalAppendSelectedHookBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\" title=\\\"Appends the selected hook into this portal\\\">Append Selected Hook</button>\\n          <button id=\\\"modalSaveBtn\\\" class=\\\"btn primary\\\" type=\\\"button\\\">Save Portal</button>\\n          <button id=\\\"modalCloseBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\">Close</button>\\n        </div>\\n      </div>\\n\\n      <div class=\\\"modalBody\\\">\\n        <div>\\n          <div class=\\\"fieldLabel\\\">Portal content (manual typing allowed)</div>\\n          <textarea id=\\\"modalText\\\" spellcheck=\\\"false\\\" aria-label=\\\"Portal content editor\\\"></textarea>\\n        </div>\\n\\n        <div>\\n          <div class=\\\"fieldLabel\\\">Imported hooks menu (bracketed strings)</div>\\n          <div id=\\\"hookList\\\" class=\\\"hookList\\\" aria-label=\\\"Hooks list\\\">\\n            <div style=\\\"color:var(--muted);font-size:12px\\\">No hooks imported yet. Use “Import Hooks JSON”.</div>\\n          </div>\\n          <div style=\\\"margin-top:10px;color:var(--muted);font-size:12px\\\">\\n            Active hook: <span id=\\\"activeHookLabel\\\" class=\\\"pill\\\">None</span>\\n          </div>\\n        </div>\\n      </div>\\n    </div>\\n  </div>\\n\\n  <!-- Imports Manager modal -->\\n  <div id=\\\"importsBg\\\" class=\\\"modalBg\\\" role=\\\"dialog\\\" aria-modal=\\\"true\\\" aria-labelledby=\\\"importsTitle\\\" aria-hidden=\\\"true\\\">\\n    <div class=\\\"modal\\\" style=\\\"max-width:980px\\\">\\n      <div class=\\\"modalTop\\\">\\n        <div>\\n          <div id=\\\"importsTitle\\\" class=\\\"t\\\">Imports Manager — Hook Packs</div>\\n          <div class=\\\"s\\\">Active hooks in the UI are the merged union of all imported JSON files.</div>\\n        </div>\\n        <div style=\\\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\\\">\\n          <button id=\\\"importsMergeBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\" title=\\\"Rebuild merged hooks from all imports\\\">Merge / Rebuild</button>\\n          <button id=\\\"importsExportAllBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\" title=\\\"Export all imported JSON files as a JSON string array for re-upload later\\\">Export All Imports</button>\\n          <button id=\\\"importsClearAllBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\" title=\\\"Clear ALL imported JSON files and merged hooks from this device\\\">Clear All Imports</button>\\n          <button id=\\\"importsCloseBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\">Close</button>\\n        </div>\\n      </div>\\n\\n      <div class=\\\"modalBody\\\" style=\\\"grid-template-columns:1fr\\\">\\n        <div>\\n          <div class=\\\"fieldLabel\\\">Active imports (this device)</div>\\n\\n          <div class=\\\"importsSummary\\\" aria-label=\\\"Imports summary\\\">\\n            <span class=\\\"pill\\\">files: <span id=\\\"importsCountPill\\\">0</span></span>\\n            <span class=\\\"pill\\\">merged hooks: <span id=\\\"importsMergedHooksPill\\\">0</span></span>\\n            <span class=\\\"pill\\\">active in UI: <span id=\\\"importsActiveModePill\\\">MERGED</span></span>\\n          </div>\\n\\n          <div style=\\\"margin-top:10px\\\" class=\\\"importsList\\\" id=\\\"importsList\\\" aria-label=\\\"Imported JSON files list\\\">\\n            <div class=\\\"miniNote\\\">No imports yet. Use “Import Hooks JSON” to add one or more files.</div>\\n          </div>\\n\\n          <div style=\\\"margin-top:10px\\\" class=\\\"miniNote\\\">\\n            Export format is a JSON array of strings: each string is the original imported file text. Re-upload later to rebuild hook packs exactly.\\n          </div>\\n        </div>\\n      </div>\\n    </div>\\n  </div>\\n\\n  <!-- Prompt Entries Completed modal -->\\n  <div id=\\\"promptEntriesBg\\\" class=\\\"modalBg\\\" role=\\\"dialog\\\" aria-modal=\\\"true\\\" aria-labelledby=\\\"promptEntriesTitle\\\" aria-hidden=\\\"true\\\">\\n    <div class=\\\"modal\\\" style=\\\"max-width:980px\\\">\\n      <div class=\\\"modalTop\\\">\\n        <div>\\n          <div id=\\\"promptEntriesTitle\\\" class=\\\"t\\\">PROMPT ENTRIES COMPLETED</div>\\n          <div class=\\\"s\\\">This window shows ONLY Layer 1 + Layer 2 (ready to paste). Layer 3 stays in the full receipt as proof.</div>\\n        </div>\\n        <div style=\\\"display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end\\\">\\n          <button id=\\\"promptEntriesCopyBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\">Copy</button>\\n          <button id=\\\"promptEntriesExportBtn\\\" class=\\\"btn secondary\\\" type=\\\"button\\\">Export</button>\\n          <button id=\\\"promptEntriesClearBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\">Clear</button>\\n          <button id=\\\"promptEntriesCloseBtn\\\" class=\\\"btn danger\\\" type=\\\"button\\\">Close</button>\\n        </div>\\n      </div>\\n\\n      <div class=\\\"modalBody\\\" style=\\\"grid-template-columns:1fr\\\">\\n        <div>\\n          <pre id=\\\"promptEntriesPre\\\" aria-label=\\\"Prompt entries completed output\\\">No prompt entries completed yet. Mint a receipt first.</pre>\\n        </div>\\n      </div>\\n    </div>\\n  </div>\\n\\n  <!-- Toast -->\\n  <div class=\\\"toastWrap\\\" aria-hidden=\\\"false\\\">\\n    <div id=\\\"toast\\\" class=\\\"toast\\\" role=\\\"status\\\" aria-live=\\\"polite\\\" aria-atomic=\\\"true\\\">\\n      <div class=\\\"msg\\\" id=\\\"toastMsg\\\">Ready.</div>\\n      <div class=\\\"tag\\\" id=\\\"toastTag\\\">MH8</div>\\n    </div>\\n  </div>\\n\\n  <script>\\n  (function () {\\n    \\\"use strict\\\";\\n\\n    // =========================\\n    // MUSIC PORTALS (schema)\\n    // =========================\\n    // NOTE: All portals are text so hooks can be injected anywhere.\\n    var PORTAL_KEYS = [\\n      \\\"MODEL_BEHAVIOR_CONTROL\\\",\\n      \\\"LYRICS\\\",\\n      \\\"GENRE\\\",\\n      \\\"MOOD\\\",\\n      \\\"ENERGY\\\",\\n      \\\"VOCAL_INTENSITY\\\",\\n      \\\"ERA_INFLUENCE\\\",\\n      \\\"BPM\\\",\\n      \\\"KEY_SIGNATURE\\\",\\n      \\\"BACKING_VOX\\\",\\n      \\\"DRUMS\\\",\\n      \\\"GUITAR\\\",\\n      \\\"BASS\\\",\\n      \\\"PIANO_KEYS\\\",\\n      \\\"SYNTH\\\",\\n      \\\"PERCUSSION\\\",\\n      \\\"DYNAMICS\\\",\\n      \\\"MIX\\\",\\n      \\\"MASTER\\\",\\n      \\\"EXTRA_HOOKS\\\",\\n      \\\"NOTES\\\",\\n      \\\"CONTINUITY_LOCK\\\",\\n      \\\"SHA256_ID\\\"\\n    ];\\n\\n    // Tier flows (flow-only; all portals still exist, mint/export/clear exactly the same)\\n    var QUICK_KEYS = [\\\"MODEL_BEHAVIOR_CONTROL\\\",\\\"LYRICS\\\",\\\"GENRE\\\",\\\"MOOD\\\",\\\"ENERGY\\\",\\\"VOCAL_INTENSITY\\\",\\\"SHA256_ID\\\"];\\n    var SEMI_KEYS  = [\\\"MODEL_BEHAVIOR_CONTROL\\\",\\\"LYRICS\\\",\\\"GENRE\\\",\\\"MOOD\\\",\\\"ENERGY\\\",\\\"VOCAL_INTENSITY\\\",\\\"ERA_INFLUENCE\\\",\\\"BPM\\\",\\\"KEY_SIGNATURE\\\",\\\"BACKING_VOX\\\",\\\"DRUMS\\\",\\\"BASS\\\",\\\"GUITAR\\\",\\\"SYNTH\\\",\\\"MIX\\\",\\\"MASTER\\\",\\\"EXTRA_HOOKS\\\",\\\"CONTINUITY_LOCK\\\",\\\"SHA256_ID\\\"];\\n    var ADV_KEYS   = PORTAL_KEYS.slice(0);\\n\\n    // Optional: default copy block for NOTES (kept empty by default)\\n    var DEFAULT_NOTES = \\\"\\\";\\n\\n    // =========================\\n    // DOM\\n    // =========================\\n    var platformSelect = document.getElementById(\\\"platformSelect\\\");\\n    var themeBtn = document.getElementById(\\\"themeBtn\\\");\\n    var logBtn = document.getElementById(\\\"logBtn\\\");\\n    var importBtn = document.getElementById(\\\"importBtn\\\");\\n    var importMgrBtn = document.getElementById(\\\"importMgrBtn\\\");\\n    var importFile = document.getElementById(\\\"importFile\\\");\\n\\n    var tablist = document.getElementById(\\\"tablist\\\");\\n    var addTabBtn = document.getElementById(\\\"addTabBtn\\\");\\n\\n    var portalStage = document.getElementById(\\\"portalStage\\\");\\n    var portalDots = document.getElementById(\\\"portalDots\\\");\\n    var portalPrevBtn = document.getElementById(\\\"portalPrevBtn\\\");\\n    var portalNextBtn = document.getElementById(\\\"portalNextBtn\\\");\\n    var portalJump = document.getElementById(\\\"portalJump\\\");\\n    var portalCounter = document.getElementById(\\\"portalCounter\\\");\\n\\n    var modeQuickBtn = document.getElementById(\\\"modeQuickBtn\\\");\\n    var modeSemiBtn = document.getElementById(\\\"modeSemiBtn\\\");\\n    var modeAdvBtn = document.getElementById(\\\"modeAdvBtn\\\");\\n    var modeLabelPill = document.getElementById(\\\"modeLabelPill\\\");\\n\\n    var activePortalName = document.getElementById(\\\"activePortalName\\\");\\n    var continuityStatusPill = document.getElementById(\\\"continuityStatusPill\\\");\\n\\n    var mintBtn = document.getElementById(\\\"mintBtn\\\");\\n    var copyBtn = document.getElementById(\\\"copyBtn\\\");\\n    var exportBtn = document.getElementById(\\\"exportBtn\\\");\\n    var clearReceiptBtn = document.getElementById(\\\"clearReceiptBtn\\\");\\n    var clearEditorBtn = document.getElementById(\\\"clearEditorBtn\\\");\\n\\n    var receiptPre = document.getElementById(\\\"receiptPre\\\");\\n    var legacyCopyArea = document.getElementById(\\\"legacyCopyArea\\\");\\n\\n    var receiptToggleBtn = document.getElementById(\\\"receiptToggleBtn\\\");\\n    var receiptChev = document.getElementById(\\\"receiptChev\\\");\\n    var receiptBody = document.getElementById(\\\"receiptBody\\\");\\n\\n    var promptEntriesBtn = document.getElementById(\\\"promptEntriesBtn\\\");\\n    var promptEntriesBg = document.getElementById(\\\"promptEntriesBg\\\");\\n    var promptEntriesCloseBtn = document.getElementById(\\\"promptEntriesCloseBtn\\\");\\n    var promptEntriesCopyBtn = document.getElementById(\\\"promptEntriesCopyBtn\\\");\\n    var promptEntriesClearBtn = document.getElementById(\\\"promptEntriesClearBtn\\\");\\n    var promptEntriesExportBtn = document.getElementById(\\\"promptEntriesExportBtn\\\");\\n    var promptEntriesPre = document.getElementById(\\\"promptEntriesPre\\\");\\n\\n    var logCard = document.getElementById(\\\"logCard\\\");\\n    var refreshLogBtn = document.getElementById(\\\"refreshLogBtn\\\");\\n    var exportVaultBtn = document.getElementById(\\\"exportVaultBtn\\\");\\n    var clearVaultBtn = document.getElementById(\\\"clearVaultBtn\\\");\\n    var logViewport = document.getElementById(\\\"logViewport\\\");\\n\\n    var ariaStatus = document.getElementById(\\\"ariaStatus\\\");\\n    var toast = document.getElementById(\\\"toast\\\");\\n    var toastMsg = document.getElementById(\\\"toastMsg\\\");\\n    var toastTag = document.getElementById(\\\"toastTag\\\");\\n\\n    var modalBg = document.getElementById(\\\"modalBg\\\");\\n    var modalTitle = document.getElementById(\\\"modalTitle\\\");\\n    var modalSub = document.getElementById(\\\"modalSub\\\");\\n    var modalText = document.getElementById(\\\"modalText\\\");\\n    var modalAppendSelectedHookBtn = document.getElementById(\\\"modalAppendSelectedHookBtn\\\");\\n    var modalSaveBtn = document.getElementById(\\\"modalSaveBtn\\\");\\n    var modalCloseBtn = document.getElementById(\\\"modalCloseBtn\\\");\\n\\n    var hookList = document.getElementById(\\\"hookList\\\");\\n    var activeHookLabel = document.getElementById(\\\"activeHookLabel\\\");\\n\\n    // UPDATE: Portal Editor pack menu DOM\\n    var modalPackChips = document.getElementById(\\\"modalPackChips\\\");\\n    var modalActivePackPill = document.getElementById(\\\"modalActivePackPill\\\");\\n\\n    // UPDATE #2: Continuity lock modal controls DOM\\n    var lockBar = document.getElementById(\\\"lockBar\\\");\\n    var lockStatusPill = document.getElementById(\\\"lockStatusPill\\\");\\n    var lockAddBtn = document.getElementById(\\\"lockAddBtn\\\");\\n    var lockEngageBtn = document.getElementById(\\\"lockEngageBtn\\\");\\n    var lockDisengageBtn = document.getElementById(\\\"lockDisengageBtn\\\");\\n\\n    var importsBg = document.getElementById(\\\"importsBg\\\");\\n    var importsCloseBtn = document.getElementById(\\\"importsCloseBtn\\\");\\n    var importsMergeBtn = document.getElementById(\\\"importsMergeBtn\\\");\\n    var importsExportAllBtn = document.getElementById(\\\"importsExportAllBtn\\\");\\n    var importsClearAllBtn = document.getElementById(\\\"importsClearAllBtn\\\");\\n    var importsList = document.getElementById(\\\"importsList\\\");\\n    var importsCountPill = document.getElementById(\\\"importsCountPill\\\");\\n    var importsMergedHooksPill = document.getElementById(\\\"importsMergedHooksPill\\\");\\n    var importsActiveModePill = document.getElementById(\\\"importsActiveModePill\\\");\\n\\n    // =========================\\n    // STATE\\n    // =========================\\n    var state = {\\n      platform: \\\"suno\\\",\\n      theme: \\\"dark\\\",\\n      showLog: false,\\n      receiptText: \\\"\\\",\\n      promptEntriesText: \\\"\\\",\\n      receiptOpen: true,\\n\\n      tabs: [],\\n      activeId: null,\\n\\n      hooks: [],\\n      selectedHook: \\\"\\\",\\n      activePortalKey: \\\"\\\",\\n      modalPortalKey: \\\"\\\",\\n\\n      imports: [], // [{id, name, created_utc, raw_text, hooks_count}]\\n      portal_mode: \\\"quick\\\", // quick | semi | adv\\n\\n      // UPDATE: Portal Editor pack selector state\\n      modal_hook_pack_mode: \\\"merged\\\", // merged | single\\n      modal_hook_pack_id: \\\"\\\" // import.id when single\\n    };\\n\\n    // =========================\\n    // Storage safe helpers\\n    // =========================\\n    function storageGet(key, fallback) {\\n      try {\\n        var v = localStorage.getItem(key);\\n        if (v === null || v === undefined) return fallback;\\n        return v;\\n      } catch (e) { return fallback; }\\n    }\\n    function storageSet(key, value) { try { localStorage.setItem(key, value); return true; } catch (e) { return false; } }\\n    function storageRemove(key) { try { localStorage.removeItem(key); return true; } catch (e) { return false; } }\\n    function nowISO() { return new Date().toISOString(); }\\n\\n    // =========================\\n    // Toast / ARIA\\n    // =========================\\n    var toastTimer = null;\\n    function announce(msg) { ariaStatus.textContent = msg; }\\n    function showToast(msg, tag) {\\n      if (tag) toastTag.textContent = tag;\\n      toastMsg.textContent = msg;\\n      toast.className = \\\"toast show\\\";\\n      announce(msg);\\n      if (toastTimer) clearTimeout(toastTimer);\\n      toastTimer = setTimeout(function () { toast.className = \\\"toast\\\"; }, 1800);\\n    }\\n\\n    // =========================\\n    // Theme\\n    // =========================\\n    function applyTheme(theme) {\\n      state.theme = theme;\\n      document.documentElement.setAttribute(\\\"data-theme\\\", theme);\\n      var isDark = (theme === \\\"dark\\\");\\n      themeBtn.setAttribute(\\\"aria-pressed\\\", isDark ? \\\"true\\\" : \\\"false\\\");\\n      themeBtn.textContent = isDark ? \\\"Light Mode\\\" : \\\"Dark Mode\\\";\\n      storageSet(\\\"mh8_music_theme\\\", theme);\\n    }\\n    function toggleTheme() {\\n      applyTheme(state.theme === \\\"dark\\\" ? \\\"light\\\" : \\\"dark\\\");\\n      showToast(\\\"Theme switched to \\\" + (state.theme === \\\"dark\\\" ? \\\"Dark\\\" : \\\"Light\\\") + \\\".\\\", \\\"THEME\\\");\\n    }\\n\\n    // =========================\\n    // Vault\\n    // =========================\\n    function getVault() {\\n      var raw = storageGet(\\\"mh8_music_vault\\\", \\\"[]\\\");\\n      try {\\n        var arr = JSON.parse(raw);\\n        if (Object.prototype.toString.call(arr) !== \\\"[object Array]\\\") return [];\\n        return arr;\\n      } catch (e) { return []; }\\n    }\\n    function setVault(arr) { return storageSet(\\\"mh8_music_vault\\\", JSON.stringify(arr)); }\\n\\n    function esc(s) {\\n      s = String(s === undefined || s === null ? \\\"\\\" : s);\\n      return s.replace(/&/g,\\\"&amp;\\\").replace(/</g,\\\"&lt;\\\").replace(/>/g,\\\"&gt;\\\").replace(/\\\"/g,\\\"&quot;\\\").replace(/'/g,\\\"&#039;\\\");\\n    }\\n\\n    function renderVaultEntry(entry, indexFromStart) {\\n      var ts = entry && entry.created_utc ? entry.created_utc : \\\"\\\";\\n      var sha = entry && entry.sha256 ? entry.sha256 : \\\"\\\";\\n      var platform = entry && entry.payload && entry.payload.platform ? entry.payload.platform : \\\"\\\";\\n      var name = (entry && entry.payload && entry.payload.prompt_name) ? entry.payload.prompt_name : (\\\"Prompt\\\");\\n\\n      var fullText =\\n        (entry && entry.full_receipt) ? entry.full_receipt :\\n        (entry && entry.receipt_text) ? (entry.receipt_text + \\\"\\\\n\\\\n\\\" + JSON.stringify(entry, null, 2)) :\\n        JSON.stringify(entry, null, 2);\\n\\n      var id = \\\"logcopy_\\\" + String(indexFromStart);\\n\\n      return ''\\n        + '<div class=\\\"logEntry\\\">'\\n        +   '<div class=\\\"logMeta\\\">'\\n        +     '<div class=\\\"left\\\">'\\n        +       '<span class=\\\"pill\\\">' + esc(name) + '</span>'\\n        +       '<span class=\\\"pill\\\">platform:' + esc(platform) + '</span>'\\n        +       '<span class=\\\"pill\\\">ts:' + esc(ts) + '</span>'\\n        +       '<span class=\\\"pill\\\">sha:' + esc(sha.slice(0,12)) + '…</span>'\\n        +     '</div>'\\n        +     '<div class=\\\"logActions\\\">'\\n        +       '<button class=\\\"btn small\\\" type=\\\"button\\\" data-copyid=\\\"' + esc(id) + '\\\">Copy Entry</button>'\\n        +     '</div>'\\n        +   '</div>'\\n        +   '<pre class=\\\"logPre\\\" id=\\\"' + esc(id) + '\\\">' + esc(fullText) + '</pre>'\\n        + '</div>';\\n    }\\n\\n    function bindLogCopyButtons() {\\n      var btns = logViewport.querySelectorAll(\\\"button[data-copyid]\\\");\\n      for (var i = 0; i < btns.length; i++) {\\n        btns[i].onclick = function () {\\n          var id = this.getAttribute(\\\"data-copyid\\\");\\n          var el = document.getElementById(id);\\n          if (!el) return;\\n          copyAnyText(el.textContent || \\\"\\\");\\n        };\\n      }\\n    }\\n\\n    function renderVault() {\\n      var vault = getVault();\\n      if (!vault.length) {\\n        logViewport.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px\\\">No receipts logged yet.</div>';\\n        return;\\n      }\\n      var out = [];\\n      for (var i = vault.length - 1; i >= 0; i--) out.push(renderVaultEntry(vault[i], i));\\n      logViewport.innerHTML = out.join(\\\"\\\");\\n      bindLogCopyButtons();\\n    }\\n\\n    // =========================\\n    // Tabs\\n    // =========================\\n    function uid() { return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); }\\n\\n    function makeEmptyPortals() {\\n      var obj = {};\\n      for (var i = 0; i < PORTAL_KEYS.length; i++) obj[PORTAL_KEYS[i]] = \\\"\\\";\\n      obj.NOTES = DEFAULT_NOTES;\\n\\n      // UPDATE #2: Default continuity lock state visible + receipt-includable\\n      obj.CONTINUITY_LOCK = \\\"DISENGAGED\\\";\\n\\n      // UPDATE #3: SHA256 ID portal default empty\\n      if (typeof obj.SHA256_ID === \\\"undefined\\\") obj.SHA256_ID = \\\"\\\";\\n\\n      return obj;\\n    }\\n\\n    function loadTabs() {\\n      var raw = storageGet(\\\"mh8_music_tabs\\\", \\\"\\\");\\n      var rawActive = storageGet(\\\"mh8_music_active\\\", \\\"\\\");\\n      var tabs = null;\\n\\n      if (raw) { try { tabs = JSON.parse(raw); } catch (e) { tabs = null; } }\\n\\n      if (tabs && Object.prototype.toString.call(tabs) === \\\"[object Array]\\\" && tabs.length) {\\n        var migrated = [];\\n        for (var i = 0; i < tabs.length; i++) {\\n          var t = tabs[i];\\n          var portals = (t && t.portals) ? t.portals : makeEmptyPortals();\\n\\n          // UPDATE #2: Ensure continuity portal exists on older saves\\n          if (portals && typeof portals.CONTINUITY_LOCK === \\\"undefined\\\") portals.CONTINUITY_LOCK = \\\"DISENGAGED\\\";\\n\\n          // UPDATE #3: Ensure SHA256_ID exists on older saves\\n          if (portals && typeof portals.SHA256_ID === \\\"undefined\\\") portals.SHA256_ID = \\\"\\\";\\n\\n          migrated.push({\\n            id: String(t.id || uid()),\\n            name: String(t.name || (\\\"Prompt \\\" + (i + 1))),\\n            portals: portals,\\n\\n            // UPDATE #2: continuity lock meta (additive; safe migration)\\n            continuity_lock: (t && t.continuity_lock) ? t.continuity_lock : null\\n          });\\n        }\\n        tabs = migrated;\\n      }\\n\\n      if (!tabs || Object.prototype.toString.call(tabs) !== \\\"[object Array]\\\" || !tabs.length) {\\n        tabs = [{ id: \\\"1\\\", name: \\\"Prompt 1\\\", portals: makeEmptyPortals(), continuity_lock: null }];\\n        rawActive = \\\"1\\\";\\n      }\\n\\n      state.tabs = tabs;\\n      state.activeId = (rawActive && findTab(rawActive)) ? String(rawActive) : String(tabs[0].id);\\n      persistTabs();\\n    }\\n\\n    function persistTabs() {\\n      storageSet(\\\"mh8_music_tabs\\\", JSON.stringify(state.tabs));\\n      storageSet(\\\"mh8_music_active\\\", String(state.activeId));\\n    }\\n\\n    function findTab(id) {\\n      for (var i = 0; i < state.tabs.length; i++) {\\n        if (String(state.tabs[i].id) === String(id)) return state.tabs[i];\\n      }\\n      return null;\\n    }\\n\\n    function indexOfTab(id) {\\n      for (var i = 0; i < state.tabs.length; i++) if (String(state.tabs[i].id) === String(id)) return i;\\n      return 0;\\n    }\\n\\n    function prevent(e) { if (!e) return; if (e.preventDefault) e.preventDefault(); e.returnValue = false; }\\n\\n    function setActiveTab(id, focusTabBtn) {\\n      if (!findTab(id)) return;\\n      state.activeId = String(id);\\n      persistTabs();\\n      renderTabs();\\n      renderPortals();\\n      if (focusTabBtn) {\\n        var btn = document.getElementById(\\\"tabbtn_\\\" + state.activeId);\\n        if (btn && btn.focus) btn.focus();\\n      }\\n      showToast(\\\"Active: \\\" + (findTab(state.activeId).name), \\\"TABS\\\");\\n    }\\n\\n    function addTab() {\\n      var id = uid();\\n      var name = \\\"Prompt \\\" + (state.tabs.length + 1);\\n      state.tabs.push({ id: id, name: name, portals: makeEmptyPortals(), continuity_lock: null });\\n      setActiveTab(id, true);\\n      showToast(\\\"New prompt created: \\\" + name + \\\".\\\", \\\"TABS\\\");\\n    }\\n\\n    function deleteTab(id) {\\n      if (state.tabs.length <= 1) { showToast(\\\"Cannot delete the last remaining prompt.\\\", \\\"TABS\\\"); return; }\\n      var idx = -1;\\n      for (var i = 0; i < state.tabs.length; i++) if (String(state.tabs[i].id) === String(id)) { idx = i; break; }\\n      if (idx < 0) return;\\n\\n      var wasActive = (String(state.activeId) === String(id));\\n      var name = state.tabs[idx].name;\\n\\n      state.tabs.splice(idx, 1);\\n      if (wasActive) {\\n        var newActive = state.tabs[Math.max(0, idx - 1)].id;\\n        state.activeId = String(newActive);\\n      }\\n      persistTabs();\\n      renderTabs();\\n      renderPortals();\\n      showToast(\\\"Deleted: \\\" + name + \\\".\\\", \\\"TABS\\\");\\n    }\\n\\n    function renderTabs() {\\n      while (tablist.firstChild) tablist.removeChild(tablist.firstChild);\\n\\n      for (var i = 0; i < state.tabs.length; i++) (function (tab) {\\n        var wrap = document.createElement(\\\"div\\\");\\n        wrap.className = \\\"tabWrap\\\";\\n\\n        var btn = document.createElement(\\\"button\\\");\\n        btn.type = \\\"button\\\";\\n        btn.className = \\\"tab\\\";\\n        btn.id = \\\"tabbtn_\\\" + tab.id;\\n        btn.setAttribute(\\\"role\\\", \\\"tab\\\");\\n        btn.setAttribute(\\\"tabindex\\\", String(tab.id === state.activeId ? \\\"0\\\" : \\\"-1\\\"));\\n        btn.setAttribute(\\\"aria-selected\\\", tab.id === state.activeId ? \\\"true\\\" : \\\"false\\\");\\n        btn.textContent = tab.name;\\n        btn.onclick = function () { setActiveTab(tab.id, true); };\\n\\n        btn.onkeydown = function (e) {\\n          e = e || window.event;\\n          var key = e.key || e.keyCode;\\n          var idx = indexOfTab(tab.id);\\n          if (key === \\\"ArrowLeft\\\" || key === 37) {\\n            prevent(e);\\n            var prev = (idx - 1 + state.tabs.length) % state.tabs.length;\\n            setActiveTab(state.tabs[prev].id, true);\\n          } else if (key === \\\"ArrowRight\\\" || key === 39) {\\n            prevent(e);\\n            var next = (idx + 1) % state.tabs.length;\\n            setActiveTab(state.tabs[next].id, true);\\n          } else if (key === \\\"Home\\\" || key === 36) {\\n            prevent(e); setActiveTab(state.tabs[0].id, true);\\n          } else if (key === \\\"End\\\" || key === 35) {\\n            prevent(e); setActiveTab(state.tabs[state.tabs.length - 1].id, true);\\n          }\\n        };\\n\\n        var del = document.createElement(\\\"button\\\");\\n        del.type = \\\"button\\\";\\n        del.className = \\\"tabDel\\\";\\n        del.setAttribute(\\\"aria-label\\\", \\\"Delete \\\" + tab.name);\\n        del.title = \\\"Delete this prompt\\\";\\n        del.innerHTML = \\\"✕\\\";\\n        del.onclick = function (ev) {\\n          if (ev && ev.preventDefault) ev.preventDefault();\\n          deleteTab(tab.id);\\n        };\\n\\n        wrap.appendChild(btn);\\n        wrap.appendChild(del);\\n        tablist.appendChild(wrap);\\n      })(state.tabs[i]);\\n    }\\n\\n    // =========================\\n    // UPDATE #2: Continuity Lock (per-tab)\\n    // =========================\\n    function getTabContinuity(tab) {\\n      if (!tab) return null;\\n      if (!tab.continuity_lock || Object.prototype.toString.call(tab.continuity_lock) !== \\\"[object Object]\\\") {\\n        tab.continuity_lock = {\\n          engaged: false,\\n          created_utc: \\\"\\\",\\n          genre: \\\"\\\",\\n          bpm: \\\"\\\",\\n          key_signature: \\\"\\\"\\n        };\\n      }\\n      if (typeof tab.continuity_lock.engaged !== \\\"boolean\\\") tab.continuity_lock.engaged = false;\\n      tab.continuity_lock.genre = String(tab.continuity_lock.genre || \\\"\\\");\\n      tab.continuity_lock.bpm = String(tab.continuity_lock.bpm || \\\"\\\");\\n      tab.continuity_lock.key_signature = String(tab.continuity_lock.key_signature || \\\"\\\");\\n      tab.continuity_lock.created_utc = String(tab.continuity_lock.created_utc || \\\"\\\");\\n      return tab.continuity_lock;\\n    }\\n\\n    function formatContinuityLockText(lock) {\\n      lock = lock || { engaged:false, created_utc:\\\"\\\", genre:\\\"\\\", bpm:\\\"\\\", key_signature:\\\"\\\" };\\n      if (!lock.engaged) return \\\"DISENGAGED\\\";\\n      var out = [];\\n      out.push(\\\"ENGAGED\\\");\\n      out.push(\\\"LOCKED_UTC: \\\" + String(lock.created_utc || \\\"\\\"));\\n      out.push(\\\"GENRE: \\\" + String(lock.genre || \\\"\\\").trim());\\n      out.push(\\\"BPM: \\\" + String(lock.bpm || \\\"\\\").trim());\\n      out.push(\\\"KEY_SIGNATURE: \\\" + String(lock.key_signature || \\\"\\\").trim());\\n      return out.join(\\\"\\\\n\\\").trim();\\n    }\\n\\n    function setContinuityLockPortalFromMeta(tab) {\\n      if (!tab) return;\\n      var lock = getTabContinuity(tab);\\n      if (!tab.portals) tab.portals = makeEmptyPortals();\\n      tab.portals.CONTINUITY_LOCK = formatContinuityLockText(lock);\\n    }\\n\\n    function applyContinuityLockToPortals(tab) {\\n      if (!tab || !tab.portals) return;\\n      var lock = getTabContinuity(tab);\\n      if (!lock.engaged) return;\\n\\n      // Enforce locked values into portals\\n      tab.portals.GENRE = String(lock.genre || \\\"\\\");\\n      tab.portals.BPM = String(lock.bpm || \\\"\\\");\\n      tab.portals.KEY_SIGNATURE = String(lock.key_signature || \\\"\\\");\\n      setContinuityLockPortalFromMeta(tab);\\n    }\\n\\n    function continuityIsEngaged(tab) {\\n      var lock = getTabContinuity(tab);\\n      return !!(lock && lock.engaged);\\n    }\\n\\n    function snapshotContinuityFromCurrent(tab) {\\n      if (!tab || !tab.portals) return;\\n      var lock = getTabContinuity(tab);\\n      lock.created_utc = nowISO();\\n      lock.genre = String(tab.portals.GENRE || \\\"\\\");\\n      lock.bpm = String(tab.portals.BPM || \\\"\\\");\\n      lock.key_signature = String(tab.portals.KEY_SIGNATURE || \\\"\\\");\\n      lock.engaged = true;\\n      applyContinuityLockToPortals(tab);\\n      persistTabs();\\n    }\\n\\n    function disengageContinuity(tab) {\\n      if (!tab) return;\\n      var lock = getTabContinuity(tab);\\n      lock.engaged = false;\\n      setContinuityLockPortalFromMeta(tab);\\n      persistTabs();\\n    }\\n\\n    function engageContinuity(tab) {\\n      if (!tab) return;\\n      var lock = getTabContinuity(tab);\\n\\n      // UPDATE #4: ALWAYS capture CURRENT portal inputs at ENGAGE time\\n      // This guarantees GENRE/BPM/KEY_SIGNATURE are recognized and printed in receipts (not blank).\\n      if (!tab.portals) tab.portals = makeEmptyPortals();\\n      lock.genre = String(tab.portals.GENRE || \\\"\\\");\\n      lock.bpm = String(tab.portals.BPM || \\\"\\\");\\n      lock.key_signature = String(tab.portals.KEY_SIGNATURE || \\\"\\\");\\n\\n      if (!lock.created_utc) lock.created_utc = nowISO();\\n      lock.engaged = true;\\n      applyContinuityLockToPortals(tab);\\n      persistTabs();\\n    }\\n\\n    function renderContinuityStatusPill() {\\n      if (!continuityStatusPill) return;\\n      var tab = findTab(state.activeId);\\n      if (!tab) { continuityStatusPill.textContent = \\\"continuity: DISENGAGED\\\"; continuityStatusPill.className = \\\"pill\\\"; return; }\\n      var engaged = continuityIsEngaged(tab);\\n      continuityStatusPill.textContent = \\\"continuity: \\\" + (engaged ? \\\"ENGAGED\\\" : \\\"DISENGAGED\\\");\\n      continuityStatusPill.className = \\\"pill\\\" + (engaged ? \\\" locked\\\" : \\\"\\\");\\n    }\\n\\n    // =========================\\n    // Flow mode helpers\\n    // =========================\\n    function keysForMode(mode) {\\n      mode = String(mode || \\\"\\\").toLowerCase();\\n      if (mode === \\\"semi\\\") return SEMI_KEYS.slice(0);\\n      if (mode === \\\"adv\\\") return ADV_KEYS.slice(0);\\n      return QUICK_KEYS.slice(0);\\n    }\\n    function normalizeActivePortalForMode() {\\n      var keys = keysForMode(state.portal_mode);\\n      for (var i = 0; i < keys.length; i++) if (String(keys[i]) === String(state.activePortalKey)) return;\\n      state.activePortalKey = keys.length ? keys[0] : \\\"LYRICS\\\";\\n    }\\n    function renderModeButtons() {\\n      var m = String(state.portal_mode || \\\"quick\\\");\\n      var isQ = (m === \\\"quick\\\"), isS = (m === \\\"semi\\\"), isA = (m === \\\"adv\\\");\\n\\n      modeQuickBtn.className = \\\"modeBtn\\\" + (isQ ? \\\" active\\\" : \\\"\\\");\\n      modeSemiBtn.className  = \\\"modeBtn\\\" + (isS ? \\\" active\\\" : \\\"\\\");\\n      modeAdvBtn.className   = \\\"modeBtn\\\" + (isA ? \\\" active\\\" : \\\"\\\");\\n\\n      modeQuickBtn.setAttribute(\\\"aria-pressed\\\", isQ ? \\\"true\\\" : \\\"false\\\");\\n      modeSemiBtn.setAttribute(\\\"aria-pressed\\\",  isS ? \\\"true\\\" : \\\"false\\\");\\n      modeAdvBtn.setAttribute(\\\"aria-pressed\\\",   isA ? \\\"true\\\" : \\\"false\\\");\\n\\n      modeLabelPill.textContent = \\\"mode: \\\" + (isQ ? \\\"QUICK\\\" : isS ? \\\"SEMI\\\" : \\\"ADV\\\");\\n    }\\n    function setPortalMode(mode, announceIt) {\\n      mode = String(mode || \\\"\\\").toLowerCase();\\n      if (mode !== \\\"quick\\\" && mode !== \\\"semi\\\" && mode !== \\\"adv\\\") mode = \\\"quick\\\";\\n      state.portal_mode = mode;\\n      storageSet(\\\"mh8_music_portal_mode\\\", mode);\\n      normalizeActivePortalForMode();\\n      renderModeButtons();\\n      renderPortals();\\n      if (announceIt) showToast(\\\"Flow mode: \\\" + (mode === \\\"quick\\\" ? \\\"Quick Start\\\" : mode === \\\"semi\\\" ? \\\"Semi Pro\\\" : \\\"Advanced Full Stack\\\") + \\\".\\\", \\\"FLOW\\\");\\n    }\\n\\n    // =========================\\n    // Portals render + edit (carousel)\\n    // =========================\\n    function portalIndexFromKey(key) {\\n      var keys = keysForMode(state.portal_mode);\\n      for (var i = 0; i < keys.length; i++) if (String(keys[i]) === String(key)) return i;\\n      return 0;\\n    }\\n    function setActivePortalByIndex(idx, announceIt) {\\n      var keys = keysForMode(state.portal_mode);\\n      idx = Number(idx);\\n      if (isNaN(idx)) idx = 0;\\n      if (idx < 0) idx = 0;\\n      if (idx >= keys.length) idx = keys.length - 1;\\n      state.activePortalKey = keys[idx];\\n      renderPortals();\\n      if (announceIt) showToast(\\\"Portal: [\\\" + state.activePortalKey + \\\"].\\\", \\\"PORTAL\\\");\\n    }\\n    function goPrevPortal() { setActivePortalByIndex(portalIndexFromKey(state.activePortalKey) - 1, true); }\\n    function goNextPortal() { setActivePortalByIndex(portalIndexFromKey(state.activePortalKey) + 1, true); }\\n\\n    function prettyPortalLabel(key) {\\n      var map = {\\n        MODEL_BEHAVIOR_CONTROL: \\\"MODEL BEHAVIOR CONTROL\\\",\\n        LYRICS: \\\"LYRICS\\\",\\n        GENRE: \\\"GENRE\\\",\\n        MOOD: \\\"MOOD\\\",\\n        ENERGY: \\\"ENERGY\\\",\\n        VOCAL_INTENSITY: \\\"VOCAL INTENSITY\\\",\\n        ERA_INFLUENCE: \\\"ERA / INFLUENCE\\\",\\n        BPM: \\\"BPM\\\",\\n        KEY_SIGNATURE: \\\"KEY SIGNATURE\\\",\\n        BACKING_VOX: \\\"BACKING VOX\\\",\\n        DRUMS: \\\"DRUMS\\\",\\n        GUITAR: \\\"GUITAR\\\",\\n        BASS: \\\"BASS\\\",\\n        PIANO_KEYS: \\\"PIANO / KEYS\\\",\\n        SYNTH: \\\"SYNTH\\\",\\n        PERCUSSION: \\\"PERCUSSION\\\",\\n        DYNAMICS: \\\"DYNAMICS\\\",\\n        MIX: \\\"MIX\\\",\\n        MASTER: \\\"MASTER\\\",\\n        EXTRA_HOOKS: \\\"EXTRA HOOKS\\\",\\n        NOTES: \\\"NOTES\\\",\\n        CONTINUITY_LOCK: \\\"CONTINUITY LOCK\\\",\\n        SHA256_ID: \\\"SHA256 ID\\\"\\n      };\\n      return map[key] || key;\\n    }\\n\\n    function renderPortals() {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n\\n      // UPDATE #2: enforce lock into visible portals (no drift)\\n      applyContinuityLockToPortals(tab);\\n\\n      // UPDATE #3: ensure SHA256_ID exists\\n      if (tab.portals && typeof tab.portals.SHA256_ID === \\\"undefined\\\") tab.portals.SHA256_ID = \\\"\\\";\\n\\n      normalizeActivePortalForMode();\\n      renderModeButtons();\\n\\n      var keys = keysForMode(state.portal_mode);\\n      if (!state.activePortalKey) state.activePortalKey = keys.length ? keys[0] : \\\"LYRICS\\\";\\n      var idx = portalIndexFromKey(state.activePortalKey);\\n      var key = keys[idx];\\n      state.activePortalKey = key;\\n\\n      while (portalJump.firstChild) portalJump.removeChild(portalJump.firstChild);\\n      for (var i = 0; i < keys.length; i++) {\\n        var opt = document.createElement(\\\"option\\\");\\n        opt.value = String(i);\\n        opt.textContent = (i + 1) + \\\". [\\\" + prettyPortalLabel(keys[i]) + \\\"]\\\";\\n        if (i === idx) opt.selected = true;\\n        portalJump.appendChild(opt);\\n      }\\n\\n      portalCounter.textContent = String(idx + 1) + \\\"/\\\" + String(keys.length);\\n\\n      while (portalDots.firstChild) portalDots.removeChild(portalDots.firstChild);\\n      for (i = 0; i < keys.length; i++) (function (dotIndex) {\\n        var d = document.createElement(\\\"button\\\");\\n        d.type = \\\"button\\\";\\n        d.className = \\\"dot\\\" + (dotIndex === idx ? \\\" active\\\" : \\\"\\\");\\n        d.setAttribute(\\\"aria-label\\\", \\\"Go to portal \\\" + (dotIndex + 1));\\n        d.onclick = function () { setActivePortalByIndex(dotIndex, false); };\\n        portalDots.appendChild(d);\\n      })(i);\\n\\n      while (portalStage.firstChild) portalStage.removeChild(portalStage.firstChild);\\n\\n      var val = (tab.portals && tab.portals[key]) ? String(tab.portals[key]) : \\\"\\\";\\n\\n      var portal = document.createElement(\\\"div\\\");\\n\\n      // UPDATE #2: mark locked portals when continuity lock engaged\\n      var engaged = continuityIsEngaged(tab);\\n      var isLockedPortal = engaged && (key === \\\"GENRE\\\" || key === \\\"BPM\\\" || key === \\\"KEY_SIGNATURE\\\");\\n      portal.className = \\\"portal active\\\" + (isLockedPortal ? \\\" locked\\\" : \\\"\\\");\\n      portal.setAttribute(\\\"role\\\", \\\"button\\\");\\n      portal.setAttribute(\\\"tabindex\\\", \\\"0\\\");\\n      portal.setAttribute(\\\"aria-label\\\", \\\"Portal \\\" + key + \\\". Click to edit.\\\");\\n\\n      var head = document.createElement(\\\"div\\\");\\n      head.className = \\\"pHead\\\";\\n\\n      var title = document.createElement(\\\"div\\\");\\n      title.className = \\\"pTitle\\\";\\n      title.textContent = \\\"[\\\" + prettyPortalLabel(key) + \\\"]\\\";\\n\\n      var badge = document.createElement(\\\"div\\\");\\n      badge.className = \\\"pBadge\\\";\\n      if (isLockedPortal) badge.textContent = \\\"LOCKED\\\";\\n      else badge.textContent = val ? (String(val.length) + \\\" chars\\\") : \\\"empty\\\";\\n\\n      head.appendChild(title);\\n      head.appendChild(badge);\\n\\n      var body = document.createElement(\\\"div\\\");\\n      body.className = \\\"pValue\\\";\\n      if (!val) body.innerHTML = '<span class=\\\"pEmpty\\\">Click to add… (type or assign hooks)</span>';\\n      else body.textContent = val;\\n\\n      portal.appendChild(head);\\n      portal.appendChild(body);\\n\\n      portal.onclick = function () { openPortalModal(key); };\\n      portal.onkeydown = function (e) {\\n        e = e || window.event;\\n        var k = e.key || e.keyCode;\\n        if (k === \\\"Enter\\\" || k === \\\" \\\" || k === 13 || k === 32) { prevent(e); openPortalModal(key); }\\n      };\\n\\n      portalStage.appendChild(portal);\\n\\n      activePortalName.textContent = \\\"[\\\" + prettyPortalLabel(key) + \\\"]\\\";\\n\\n      portalPrevBtn.disabled = (idx <= 0);\\n      portalNextBtn.disabled = (idx >= keys.length - 1);\\n\\n      // UPDATE #2: continuity status in the editor footer row\\n      renderContinuityStatusPill();\\n    }\\n\\n    function updatePortalValue(portalKey, newValue) {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n      if (!tab.portals) tab.portals = makeEmptyPortals();\\n\\n      // UPDATE #2: block updates to locked fields when engaged\\n      if (continuityIsEngaged(tab) && (portalKey === \\\"GENRE\\\" || portalKey === \\\"BPM\\\" || portalKey === \\\"KEY_SIGNATURE\\\")) {\\n        showToast(\\\"Continuity lock ENGAGED. Editing is locked for \\\" + portalKey + \\\". Disengage to edit.\\\", \\\"LOCK\\\");\\n        applyContinuityLockToPortals(tab);\\n        persistTabs();\\n        renderPortals();\\n        return;\\n      }\\n\\n      tab.portals[portalKey] = String(newValue || \\\"\\\");\\n\\n      // UPDATE #2: keep continuity lock portal text in sync with meta (always)\\n      if (portalKey === \\\"CONTINUITY_LOCK\\\") {\\n        var lock = getTabContinuity(tab);\\n        var txt = String(tab.portals.CONTINUITY_LOCK || \\\"\\\").trim().toUpperCase();\\n        if (txt.indexOf(\\\"ENGAGED\\\") === 0) {\\n          // If user manually typed ENGAGED, treat as engage (without snapshot change)\\n          lock.engaged = true;\\n          if (!lock.created_utc) lock.created_utc = nowISO();\\n          setContinuityLockPortalFromMeta(tab);\\n        } else if (txt.indexOf(\\\"DISENGAGED\\\") === 0 || txt === \\\"\\\") {\\n          lock.engaged = false;\\n          setContinuityLockPortalFromMeta(tab);\\n        }\\n      }\\n\\n      persistTabs();\\n      renderPortals();\\n    }\\n\\n    // =========================\\n    // Modal\\n    // =========================\\n    var lastFocusEl = null;\\n    var lastFocusElImports = null;\\n    var lastFocusElPromptEntries = null;\\n\\n    // UPDATE: helpers for pack selection inside Portal Editor\\n    function findImportById(id) {\\n      for (var i = 0; i < state.imports.length; i++) if (String(state.imports[i].id) === String(id)) return state.imports[i];\\n      return null;\\n    }\\n    function safeImportName(imp) {\\n      var n = imp && imp.name ? String(imp.name) : \\\"import\\\";\\n      if (n.length > 36) n = n.slice(0, 33) + \\\"…\\\";\\n      return n;\\n    }\\n    function setModalHookPack(mode, id, announceIt) {\\n      mode = String(mode || \\\"merged\\\");\\n      if (mode !== \\\"merged\\\" && mode !== \\\"single\\\") mode = \\\"merged\\\";\\n\\n      if (mode === \\\"single\\\") {\\n        var imp = findImportById(id);\\n        if (!imp) mode = \\\"merged\\\";\\n      }\\n\\n      state.modal_hook_pack_mode = mode;\\n      state.modal_hook_pack_id = (mode === \\\"single\\\") ? String(id || \\\"\\\") : \\\"\\\";\\n\\n      renderHookPackMenu();\\n      renderHookList();\\n\\n      if (announceIt) {\\n        if (state.modal_hook_pack_mode === \\\"merged\\\") showToast(\\\"Hook pack set to MERGED (all).\\\", \\\"HOOKS\\\");\\n        else {\\n          var imp2 = findImportById(state.modal_hook_pack_id);\\n          showToast(\\\"Hook pack selected: \\\" + (imp2 ? safeImportName(imp2) : \\\"import\\\") + \\\".\\\", \\\"HOOKS\\\");\\n        }\\n      }\\n    }\\n    function renderHookPackMenu() {\\n      if (!modalPackChips) return;\\n\\n      while (modalPackChips.firstChild) modalPackChips.removeChild(modalPackChips.firstChild);\\n\\n      // Always include MERGED\\n      var mergedBtn = document.createElement(\\\"button\\\");\\n      mergedBtn.type = \\\"button\\\";\\n      mergedBtn.className = \\\"packChip\\\" + (state.modal_hook_pack_mode === \\\"merged\\\" ? \\\" active\\\" : \\\"\\\");\\n      mergedBtn.setAttribute(\\\"aria-label\\\", \\\"Select MERGED hooks (all imported packs)\\\");\\n      mergedBtn.textContent = \\\"MERGED\\\";\\n      mergedBtn.onclick = function () { setModalHookPack(\\\"merged\\\", \\\"\\\", true); };\\n      modalPackChips.appendChild(mergedBtn);\\n\\n      // Add each imported file as a chip\\n      for (var i = 0; i < state.imports.length; i++) (function (imp) {\\n        var b = document.createElement(\\\"button\\\");\\n        b.type = \\\"button\\\";\\n        var isActive = (state.modal_hook_pack_mode === \\\"single\\\" && String(state.modal_hook_pack_id) === String(imp.id));\\n        b.className = \\\"packChip\\\" + (isActive ? \\\" active\\\" : \\\"\\\");\\n        b.setAttribute(\\\"aria-label\\\", \\\"Select hook pack \\\" + safeImportName(imp));\\n        b.textContent = safeImportName(imp);\\n        b.onclick = function () { setModalHookPack(\\\"single\\\", imp.id, true); };\\n        modalPackChips.appendChild(b);\\n      })(state.imports[i]);\\n\\n      // Active pill text\\n      if (modalActivePackPill) {\\n        if (state.modal_hook_pack_mode === \\\"merged\\\") {\\n          modalActivePackPill.textContent = \\\"active: MERGED\\\";\\n        } else {\\n          var impA = findImportById(state.modal_hook_pack_id);\\n          modalActivePackPill.textContent = \\\"active: \\\" + (impA ? safeImportName(impA) : \\\"MERGED\\\");\\n          if (!impA) {\\n            state.modal_hook_pack_mode = \\\"merged\\\";\\n            state.modal_hook_pack_id = \\\"\\\";\\n            modalActivePackPill.textContent = \\\"active: MERGED\\\";\\n          }\\n        }\\n      }\\n    }\\n\\n    // UPDATE #2: render continuity lock controls when applicable\\n    function renderLockBarForPortal(portalKey) {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n\\n      var lock = getTabContinuity(tab);\\n      var engaged = !!lock.engaged;\\n\\n      if (!lockBar) return;\\n      if (String(portalKey) !== \\\"CONTINUITY_LOCK\\\") {\\n        lockBar.style.display = \\\"none\\\";\\n        return;\\n      }\\n\\n      lockBar.style.display = \\\"flex\\\";\\n      if (lockStatusPill) {\\n        lockStatusPill.textContent = engaged ? \\\"ENGAGED\\\" : \\\"DISENGAGED\\\";\\n        lockStatusPill.className = \\\"lockPill\\\" + (engaged ? \\\" engaged\\\" : \\\"\\\");\\n      }\\n      if (lockEngageBtn) lockEngageBtn.disabled = engaged;\\n      if (lockDisengageBtn) lockDisengageBtn.disabled = !engaged;\\n    }\\n\\n    function isPortalLockedByContinuity(portalKey) {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return false;\\n      if (!continuityIsEngaged(tab)) return false;\\n      return (portalKey === \\\"GENRE\\\" || portalKey === \\\"BPM\\\" || portalKey === \\\"KEY_SIGNATURE\\\");\\n    }\\n\\n    function openPortalModal(portalKey) {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n\\n      state.activePortalKey = portalKey;\\n      state.modalPortalKey = portalKey;\\n\\n      // UPDATE #2: enforce lock before opening\\n      applyContinuityLockToPortals(tab);\\n\\n      // UPDATE #3: ensure SHA256_ID exists before opening\\n      if (tab.portals && typeof tab.portals.SHA256_ID === \\\"undefined\\\") tab.portals.SHA256_ID = \\\"\\\";\\n\\n      var current = (tab.portals && tab.portals[portalKey]) ? String(tab.portals[portalKey]) : \\\"\\\";\\n\\n      modalTitle.textContent = \\\"Portal Editor — [\\\" + prettyPortalLabel(portalKey) + \\\"]\\\";\\n\\n      // UPDATE #2: locked portals are read-only\\n      var locked = isPortalLockedByContinuity(portalKey);\\n      if (locked) {\\n        modalSub.textContent = \\\"Continuity lock is ENGAGED. This portal is locked (read-only). Disengage in CONTINUITY LOCK to edit.\\\";\\n        modalText.setAttribute(\\\"readonly\\\",\\\"readonly\\\");\\n      } else {\\n        modalSub.textContent = \\\"Type your entry, or click a hook to assign into this portal.\\\";\\n        modalText.removeAttribute(\\\"readonly\\\");\\n      }\\n\\n      modalText.value = current;\\n\\n      lastFocusEl = document.activeElement;\\n\\n      modalBg.style.display = \\\"flex\\\";\\n      modalBg.setAttribute(\\\"aria-hidden\\\", \\\"false\\\");\\n\\n      // UPDATE: render pack menu in Portal Editor\\n      renderHookPackMenu();\\n\\n      // UPDATE #2: continuity lock controls\\n      renderLockBarForPortal(portalKey);\\n\\n      renderHookList();\\n\\n      activePortalName.textContent = \\\"[\\\" + prettyPortalLabel(portalKey) + \\\"]\\\";\\n      showToast(\\\"Portal selected: [\\\" + prettyPortalLabel(portalKey) + \\\"].\\\", \\\"PORTAL\\\");\\n\\n      setTimeout(function () { try { modalText.focus(); } catch (e) {} }, 0);\\n    }\\n\\n    function closeModal() {\\n      modalBg.style.display = \\\"none\\\";\\n      modalBg.setAttribute(\\\"aria-hidden\\\", \\\"true\\\");\\n      state.modalPortalKey = \\\"\\\";\\n      try { if (lastFocusEl && lastFocusEl.focus) lastFocusEl.focus(); } catch (e) {}\\n    }\\n\\n    modalCloseBtn.onclick = function () { closeModal(); };\\n    modalBg.onclick = function (e) { e = e || window.event; if (e.target === modalBg) closeModal(); };\\n\\n    // Imports manager modal\\n    function openImportsManager() {\\n      lastFocusElImports = document.activeElement;\\n      importsBg.style.display = \\\"flex\\\";\\n      importsBg.setAttribute(\\\"aria-hidden\\\", \\\"false\\\");\\n      renderImportsManager();\\n      showToast(\\\"Imports Manager opened.\\\", \\\"IMPORTS\\\");\\n      setTimeout(function(){ try { importsCloseBtn.focus(); } catch(e){} }, 0);\\n    }\\n    function closeImportsManager() {\\n      importsBg.style.display = \\\"none\\\";\\n      importsBg.setAttribute(\\\"aria-hidden\\\", \\\"true\\\");\\n      try { if (lastFocusElImports && lastFocusElImports.focus) lastFocusElImports.focus(); } catch (e) {}\\n    }\\n    importsCloseBtn.onclick = function(){ closeImportsManager(); };\\n    importsBg.onclick = function(e){ e = e || window.event; if (e.target === importsBg) closeImportsManager(); };\\n\\n    // Prompt Entries modal\\n    function openPromptEntries() {\\n      lastFocusElPromptEntries = document.activeElement;\\n      promptEntriesBg.style.display = \\\"flex\\\";\\n      promptEntriesBg.setAttribute(\\\"aria-hidden\\\", \\\"false\\\");\\n      renderPromptEntries();\\n      showToast(\\\"Prompt Entries module opened.\\\", \\\"PROMPT\\\");\\n      setTimeout(function(){ try { promptEntriesCloseBtn.focus(); } catch(e){} }, 0);\\n    }\\n    function closePromptEntries() {\\n      promptEntriesBg.style.display = \\\"none\\\";\\n      promptEntriesBg.setAttribute(\\\"aria-hidden\\\", \\\"true\\\");\\n      try { if (lastFocusElPromptEntries && lastFocusElPromptEntries.focus) lastFocusElPromptEntries.focus(); } catch (e) {}\\n    }\\n    promptEntriesCloseBtn.onclick = function(){ closePromptEntries(); };\\n    promptEntriesBg.onclick = function(e){ e = e || window.event; if (e.target === promptEntriesBg) closePromptEntries(); };\\n\\n    // ESC closes whichever modal is on top\\n    document.addEventListener(\\\"keydown\\\", function (e) {\\n      var k = e.key || e.keyCode;\\n      if (k === \\\"Escape\\\" || k === 27) {\\n        if (promptEntriesBg.style.display === \\\"flex\\\") { if (e.preventDefault) e.preventDefault(); closePromptEntries(); return; }\\n        if (importsBg.style.display === \\\"flex\\\") { if (e.preventDefault) e.preventDefault(); closeImportsManager(); return; }\\n        if (modalBg.style.display === \\\"flex\\\") { if (e.preventDefault) e.preventDefault(); closeModal(); return; }\\n      }\\n    });\\n\\n    modalSaveBtn.onclick = function () {\\n      if (!state.modalPortalKey) return;\\n\\n      // UPDATE #2: if locked, do not save edits\\n      if (isPortalLockedByContinuity(state.modalPortalKey)) {\\n        showToast(\\\"This portal is locked by Continuity Lock. Disengage to edit.\\\", \\\"LOCK\\\");\\n        closeModal();\\n        return;\\n      }\\n\\n      updatePortalValue(state.modalPortalKey, modalText.value);\\n      showToast(\\\"Saved portal [\\\" + prettyPortalLabel(state.modalPortalKey) + \\\"].\\\", \\\"PORTAL\\\");\\n      closeModal();\\n    };\\n\\n    modalAppendSelectedHookBtn.onclick = function () {\\n      if (!state.modalPortalKey) return;\\n\\n      // UPDATE #2: if locked, block append\\n      if (isPortalLockedByContinuity(state.modalPortalKey)) {\\n        showToast(\\\"This portal is locked by Continuity Lock. Disengage to edit.\\\", \\\"LOCK\\\");\\n        return;\\n      }\\n\\n      if (!state.selectedHook) { showToast(\\\"No hook selected.\\\", \\\"HOOKS\\\"); return; }\\n      var cur = modalText.value || \\\"\\\";\\n      modalText.value = cur ? (cur + \\\"\\\\n\\\" + state.selectedHook) : state.selectedHook;\\n      showToast(\\\"Hook appended to portal draft.\\\", \\\"HOOKS\\\");\\n      try { modalText.focus(); } catch (e) {}\\n    };\\n\\n    // UPDATE #2: Continuity lock modal buttons\\n    if (lockAddBtn) lockAddBtn.onclick = function () {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n      snapshotContinuityFromCurrent(tab);\\n      setContinuityLockPortalFromMeta(tab);\\n      renderLockBarForPortal(\\\"CONTINUITY_LOCK\\\");\\n      renderContinuityStatusPill();\\n      renderPortals();\\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \\\"\\\";\\n      showToast(\\\"Continuity lock ADDED + ENGAGED (GENRE+BPM+KEY).\\\", \\\"LOCK\\\");\\n    };\\n    if (lockEngageBtn) lockEngageBtn.onclick = function () {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n      engageContinuity(tab); // UPDATE #4 now captures current portal inputs at engage time\\n      setContinuityLockPortalFromMeta(tab);\\n      renderLockBarForPortal(\\\"CONTINUITY_LOCK\\\");\\n      renderContinuityStatusPill();\\n      renderPortals();\\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \\\"\\\";\\n      showToast(\\\"Continuity lock ENGAGED.\\\", \\\"LOCK\\\");\\n    };\\n    if (lockDisengageBtn) lockDisengageBtn.onclick = function () {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n      disengageContinuity(tab);\\n      renderLockBarForPortal(\\\"CONTINUITY_LOCK\\\");\\n      renderContinuityStatusPill();\\n      renderPortals();\\n      if (modalText) modalText.value = tab.portals.CONTINUITY_LOCK || \\\"\\\";\\n      showToast(\\\"Continuity lock DISENGAGED.\\\", \\\"LOCK\\\");\\n    };\\n\\n    // =========================\\n    // Hooks import (bracketed strings)\\n    // =========================\\n    function parseHooksFromAnyJSON(obj) {\\n      var hooks = [];\\n      function addFromString(s) {\\n        s = String(s || \\\"\\\");\\n        var re = /\\\\[[^\\\\[\\\\]]+?\\\\]/g, m;\\n        while ((m = re.exec(s)) !== null) hooks.push(m[0]);\\n      }\\n      function walk(v, depth) {\\n        if (depth > 6) return;\\n        if (v === null || v === undefined) return;\\n        var t = Object.prototype.toString.call(v);\\n        if (t === \\\"[object String]\\\") { addFromString(v); return; }\\n        if (t === \\\"[object Array]\\\") { for (var i = 0; i < v.length; i++) walk(v[i], depth + 1); return; }\\n        if (t === \\\"[object Object]\\\") { for (var k in v) if (Object.prototype.hasOwnProperty.call(v, k)) walk(v[k], depth + 1); return; }\\n      }\\n      walk(obj, 0);\\n\\n      var seen = {}, out = [];\\n      for (var i = 0; i < hooks.length; i++) if (!seen[hooks[i]]) { seen[hooks[i]] = true; out.push(hooks[i]); }\\n      return out;\\n    }\\n\\n    // UPDATE: returns hooks for the selected pack in Portal Editor\\n    function hooksForModalView() {\\n      if (state.modal_hook_pack_mode === \\\"merged\\\") return (state.hooks && state.hooks.length) ? state.hooks : [];\\n      var imp = findImportById(state.modal_hook_pack_id);\\n      if (!imp || !imp.raw_text) return [];\\n      // Cache parsed hooks on the import record (additive; safe)\\n      if (imp._hooks_cache && Object.prototype.toString.call(imp._hooks_cache) === \\\"[object Array]\\\") return imp._hooks_cache;\\n\\n      var obj = null;\\n      try { obj = JSON.parse(imp.raw_text); } catch (e) { obj = String(imp.raw_text || \\\"\\\"); }\\n      var hooks = parseHooksFromAnyJSON(obj);\\n      imp._hooks_cache = hooks;\\n      return hooks;\\n    }\\n\\n    function renderHookList() {\\n      var listHooks = hooksForModalView();\\n\\n      if (!listHooks || !listHooks.length) {\\n        // Friendly, accurate messaging based on modal selection\\n        if (state.modal_hook_pack_mode === \\\"single\\\") {\\n          var imp = findImportById(state.modal_hook_pack_id);\\n          var label = imp ? safeImportName(imp) : \\\"import\\\";\\n          hookList.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px\\\">No bracketed hooks found in this pack: <b>' + esc(label) + '</b>. Switch to MERGED or import another file.</div>';\\n        } else {\\n          hookList.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px\\\">No hook packs imported yet. Use “Import Hooks JSON”.</div>';\\n        }\\n        activeHookLabel.textContent = \\\"None\\\";\\n        return;\\n      }\\n\\n      var html = [];\\n      for (var i = 0; i < listHooks.length; i++) {\\n        var hook = listHooks[i];\\n        html.push(\\n          '<div class=\\\"hookItem\\\">'\\n            + '<div class=\\\"hookText\\\">' + esc(hook) + '</div>'\\n            + '<div class=\\\"hookBtns\\\">'\\n              + '<button class=\\\"btn small\\\" type=\\\"button\\\" data-hook=\\\"' + esc(hook) + '\\\" data-action=\\\"select\\\">Select</button>'\\n              + '<button class=\\\"btn small secondary\\\" type=\\\"button\\\" data-hook=\\\"' + esc(hook) + '\\\" data-action=\\\"append\\\">Append</button>'\\n            + '</div>'\\n          + '</div>'\\n        );\\n      }\\n      hookList.innerHTML = html.join(\\\"\\\");\\n      bindHookButtons();\\n      activeHookLabel.textContent = state.selectedHook ? state.selectedHook : \\\"None\\\";\\n    }\\n\\n    function bindHookButtons() {\\n      var btns = hookList.querySelectorAll(\\\"button[data-action]\\\");\\n      for (var i = 0; i < btns.length; i++) {\\n        btns[i].onclick = function () {\\n          var action = this.getAttribute(\\\"data-action\\\");\\n          var hook = this.getAttribute(\\\"data-hook\\\") || \\\"\\\";\\n          if (!hook) return;\\n\\n          if (action === \\\"select\\\") {\\n            state.selectedHook = hook;\\n            activeHookLabel.textContent = hook;\\n            showToast(\\\"Hook selected.\\\", \\\"HOOKS\\\");\\n          } else if (action === \\\"append\\\") {\\n            state.selectedHook = hook;\\n            activeHookLabel.textContent = hook;\\n\\n            if (modalBg.style.display === \\\"flex\\\" && state.modalPortalKey) {\\n              // UPDATE #2: do not append into locked portals\\n              if (isPortalLockedByContinuity(state.modalPortalKey)) {\\n                showToast(\\\"This portal is locked by Continuity Lock. Disengage to edit.\\\", \\\"LOCK\\\");\\n                return;\\n              }\\n              var cur = modalText.value || \\\"\\\";\\n              modalText.value = cur ? (cur + \\\"\\\\n\\\" + hook) : hook;\\n              showToast(\\\"Hook appended to portal draft.\\\", \\\"HOOKS\\\");\\n              try { modalText.focus(); } catch (e) {}\\n            } else {\\n              showToast(\\\"Open a portal to append hooks.\\\", \\\"HOOKS\\\");\\n            }\\n          }\\n        };\\n      }\\n    }\\n\\n    // =========================\\n    // Imports Manager (multi-file)\\n    // =========================\\n    function getImportsStore() {\\n      var raw = storageGet(\\\"mh8_music_imported_json_files_v1\\\", \\\"[]\\\");\\n      try { var arr = JSON.parse(raw); return (Object.prototype.toString.call(arr) === \\\"[object Array]\\\") ? arr : []; }\\n      catch (e) { return []; }\\n    }\\n    function setImportsStore(arr) { return storageSet(\\\"mh8_music_imported_json_files_v1\\\", JSON.stringify(arr)); }\\n\\n    function rebuildMergedHooksFromImports() {\\n      var all = [], seen = {};\\n      for (var i = 0; i < state.imports.length; i++) {\\n        var imp = state.imports[i];\\n        if (!imp || !imp.raw_text) continue;\\n\\n        // Clear modal cache on rebuild (additive, safe)\\n        try { imp._hooks_cache = null; } catch (e) {}\\n\\n        var obj = null;\\n        try { obj = JSON.parse(imp.raw_text); } catch (e) { obj = String(imp.raw_text || \\\"\\\"); }\\n        var hooks = parseHooksFromAnyJSON(obj);\\n        imp.hooks_count = hooks.length;\\n\\n        for (var j = 0; j < hooks.length; j++) {\\n          var h = hooks[j];\\n          if (!seen[h]) { seen[h] = true; all.push(h); }\\n        }\\n      }\\n\\n      state.hooks = all;\\n      storageSet(\\\"mh8_music_imported_hooks\\\", JSON.stringify(all));\\n      setImportsStore(state.imports);\\n\\n      // UPDATE: keep Portal Editor pack selection valid\\n      if (state.modal_hook_pack_mode === \\\"single\\\" && state.modal_hook_pack_id) {\\n        if (!findImportById(state.modal_hook_pack_id)) {\\n          state.modal_hook_pack_mode = \\\"merged\\\";\\n          state.modal_hook_pack_id = \\\"\\\";\\n        }\\n      }\\n\\n      if (modalBg.style.display === \\\"flex\\\") {\\n        renderHookPackMenu();\\n        renderHookList();\\n      }\\n      renderImportsManager();\\n      return all.length;\\n    }\\n\\n    function addImportFileRecord(filename, rawText) {\\n      var rec = { id: uid(), name: String(filename || (\\\"import_\\\" + nowISO())), created_utc: nowISO(), raw_text: String(rawText || \\\"\\\"), hooks_count: 0 };\\n      state.imports.push(rec);\\n      var mergedCount = rebuildMergedHooksFromImports();\\n      showToast(\\\"Hook pack added. Active merged hooks: \\\" + mergedCount + \\\".\\\", \\\"IMPORT\\\");\\n    }\\n\\n    function deleteSingleImportById(id) {\\n      var idx = -1;\\n      for (var i = 0; i < state.imports.length; i++) if (String(state.imports[i].id) === String(id)) { idx = i; break; }\\n      if (idx < 0) return;\\n      var name = state.imports[idx].name;\\n\\n      state.imports.splice(idx, 1);\\n\\n      // UPDATE: if the deleted pack was active in Portal Editor, revert to MERGED\\n      if (state.modal_hook_pack_mode === \\\"single\\\" && String(state.modal_hook_pack_id) === String(id)) {\\n        state.modal_hook_pack_mode = \\\"merged\\\";\\n        state.modal_hook_pack_id = \\\"\\\";\\n      }\\n\\n      var mergedCount = rebuildMergedHooksFromImports();\\n      showToast(\\\"Deleted import: \\\" + name + \\\". Merged hooks: \\\" + mergedCount + \\\".\\\", \\\"IMPORTS\\\");\\n    }\\n\\n    function clearAllImports() {\\n      state.imports = [];\\n      state.hooks = [];\\n      storageRemove(\\\"mh8_music_imported_json_files_v1\\\");\\n      storageRemove(\\\"mh8_music_imported_hooks\\\");\\n      state.selectedHook = \\\"\\\";\\n\\n      // UPDATE: reset Portal Editor pack selection\\n      state.modal_hook_pack_mode = \\\"merged\\\";\\n      state.modal_hook_pack_id = \\\"\\\";\\n\\n      renderHookPackMenu();\\n      renderHookList();\\n      renderImportsManager();\\n      showToast(\\\"All hook packs cleared (local).\\\", \\\"IMPORTS\\\");\\n    }\\n\\n    function exportAllImportsAsStringArray() {\\n      var arr = [];\\n      for (var i = 0; i < state.imports.length; i++) arr.push(String(state.imports[i].raw_text || \\\"\\\"));\\n      var text = JSON.stringify(arr, null, 2);\\n      var ok = downloadText(\\\"mh8-music-hookpacks-string-array.json\\\", text, \\\"application/json\\\");\\n      showToast(ok ? \\\"All imports exported (string array).\\\" : \\\"Export blocked by browser.\\\", \\\"EXPORT\\\");\\n    }\\n\\n    function renderImportsManager() {\\n      var count = state.imports.length;\\n      importsCountPill.textContent = String(count);\\n      importsMergedHooksPill.textContent = String(state.hooks ? state.hooks.length : 0);\\n      importsActiveModePill.textContent = \\\"MERGED\\\";\\n\\n      if (!count) {\\n        importsList.innerHTML = '<div class=\\\"miniNote\\\">No imports yet. Use “Import Hooks JSON” to add one or more files.</div>';\\n        return;\\n      }\\n\\n      var html = [];\\n      for (var i = 0; i < state.imports.length; i++) {\\n        var imp = state.imports[i];\\n        var name = imp.name || \\\"import\\\";\\n        var ts = imp.created_utc || \\\"\\\";\\n        var hc = imp.hooks_count || 0;\\n        var bytes = (imp.raw_text && imp.raw_text.length) ? imp.raw_text.length : 0;\\n\\n        html.push(\\n          '<div class=\\\"importRow\\\">'\\n            + '<div class=\\\"importMeta\\\">'\\n              + '<div class=\\\"importName\\\">' + esc(name) + '</div>'\\n              + '<div class=\\\"importSub\\\">'\\n                + 'hooks: <b>' + esc(hc) + '</b>'\\n                + ' • chars: ' + esc(bytes)\\n                + ' • ts: ' + esc(ts)\\n              + '</div>'\\n            + '</div>'\\n            + '<div class=\\\"importBtns\\\">'\\n              + '<button class=\\\"btn small\\\" type=\\\"button\\\" data-imp=\\\"' + esc(imp.id) + '\\\" data-impact=\\\"copy\\\">Copy JSON</button>'\\n              + '<button class=\\\"btn small secondary\\\" type=\\\"button\\\" data-imp=\\\"' + esc(imp.id) + '\\\" data-impact=\\\"delete\\\">Delete</button>'\\n            + '</div>'\\n          + '</div>'\\n        );\\n      }\\n\\n      importsList.innerHTML = html.join(\\\"\\\");\\n      bindImportsManagerButtons();\\n    }\\n\\n    function bindImportsManagerButtons() {\\n      var btns = importsList.querySelectorAll(\\\"button[data-impact]\\\");\\n      for (var i = 0; i < btns.length; i++) {\\n        btns[i].onclick = function () {\\n          var act = this.getAttribute(\\\"data-impact\\\");\\n          var id = this.getAttribute(\\\"data-imp\\\");\\n          if (!id) return;\\n\\n          if (act === \\\"delete\\\") deleteSingleImportById(id);\\n          else if (act === \\\"copy\\\") {\\n            for (var j = 0; j < state.imports.length; j++) {\\n              if (String(state.imports[j].id) === String(id)) {\\n                copyAnyText(String(state.imports[j].raw_text || \\\"\\\"));\\n                showToast(\\\"Imported JSON copied.\\\", \\\"COPY\\\");\\n                break;\\n              }\\n            }\\n          }\\n        };\\n      }\\n    }\\n\\n    function handleImportedJSONText(text, filenameForManager) {\\n      var obj = null;\\n      try { obj = JSON.parse(text); } catch (e) { obj = String(text || \\\"\\\"); }\\n\\n      var hooks = parseHooksFromAnyJSON(obj);\\n      if (!hooks.length) { showToast(\\\"No bracketed hooks found in import.\\\", \\\"IMPORT\\\"); return; }\\n\\n      addImportFileRecord(filenameForManager || \\\"hookpack.json\\\", String(text || \\\"\\\"));\\n      if (modalBg.style.display === \\\"flex\\\") {\\n        renderHookPackMenu();\\n        renderHookList();\\n      }\\n    }\\n\\n    function importFromFile(file) {\\n      try {\\n        var reader = new FileReader();\\n        reader.onload = function () { handleImportedJSONText(String(reader.result || \\\"\\\"), (file && file.name) ? file.name : \\\"hookpack.json\\\"); };\\n        reader.onerror = function () { showToast(\\\"Import failed (file read).\\\", \\\"IMPORT\\\"); };\\n        reader.readAsText(file);\\n      } catch (e) { showToast(\\\"Import failed (unsupported).\\\", \\\"IMPORT\\\"); }\\n    }\\n\\n    // =========================\\n    // SHA-256 (crypto.subtle + fallback)\\n    // =========================\\n    function utf8Bytes(str) {\\n      if (window.TextEncoder) return new TextEncoder().encode(str);\\n      var utf8 = unescape(encodeURIComponent(str));\\n      var arr = new Array(utf8.length);\\n      for (var i = 0; i < utf8.length; i++) arr[i] = utf8.charCodeAt(i);\\n      return arr;\\n    }\\n    function bytesToHex(bytes) {\\n      var hex = \\\"\\\", i, b, h;\\n      for (i = 0; i < bytes.length; i++) { b = bytes[i] & 255; h = b.toString(16); if (h.length < 2) h = \\\"0\\\" + h; hex += h; }\\n      return hex;\\n    }\\n    function sha256Fallback(ascii) {\\n      function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); }\\n      var mathPow = Math.pow, maxWord = mathPow(2, 32), lengthProperty = \\\"length\\\";\\n      var i, j, result = \\\"\\\", words = [], asciiBitLength = ascii[lengthProperty] * 8;\\n      var hash = sha256Fallback.h = sha256Fallback.h || [];\\n      var k = sha256Fallback.k = sha256Fallback.k || [];\\n      var primeCounter = k[lengthProperty], isComposite = {};\\n      for (var candidate = 2; primeCounter < 64; candidate++) {\\n        if (!isComposite[candidate]) {\\n          for (i = 0; i < 313; i += candidate) isComposite[i] = candidate;\\n          hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0;\\n          k[primeCounter++] = (mathPow(candidate, 1/3) * maxWord) | 0;\\n        }\\n      }\\n      ascii += \\\"\\\\x80\\\";\\n      while (ascii[lengthProperty] % 64 - 56) ascii += \\\"\\\\x00\\\";\\n      for (i = 0; i < ascii[lengthProperty]; i++) { j = ascii.charCodeAt(i); words[i >> 2] |= j << ((3 - i) % 4) * 8; }\\n      words[words[lengthProperty]] = (asciiBitLength / maxWord) | 0;\\n      words[words[lengthProperty]] = (asciiBitLength);\\n      for (j = 0; j < words[lengthProperty];) {\\n        var w = words.slice(j, j += 16), oldHash = hash.slice(0);\\n        for (i = 0; i < 64; i++) {\\n          var w15 = w[i - 15], w2 = w[i - 2];\\n          var a = hash[0], e = hash[4];\\n          var temp1 = hash[7]\\n            + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25))\\n            + ((e & hash[5]) ^ ((~e) & hash[6]))\\n            + k[i]\\n            + (w[i] = (i < 16) ? w[i] : (\\n              w[i - 16]\\n              + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3))\\n              + w[i - 7]\\n              + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))\\n            ) | 0);\\n          var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22))\\n            + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));\\n          hash = [(temp1 + temp2) | 0].concat(hash);\\n          hash[4] = (hash[4] + temp1) | 0;\\n          hash.pop();\\n        }\\n        for (i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;\\n      }\\n      for (i = 0; i < 8; i++) {\\n        for (j = 3; j + 1; j--) {\\n          var bb = (hash[i] >> (j * 8)) & 255;\\n          result += ((bb < 16) ? \\\"0\\\" : \\\"\\\") + bb.toString(16);\\n        }\\n      }\\n      return result;\\n    }\\n    function sha256Hex(message, cb) {\\n      try {\\n        if (window.crypto && window.crypto.subtle) {\\n          var bytes = utf8Bytes(message);\\n          var ab = bytes.buffer ? bytes.buffer : (new Uint8Array(bytes)).buffer;\\n          window.crypto.subtle.digest(\\\"SHA-256\\\", ab).then(function (hashBuffer) {\\n            cb(null, bytesToHex(new Uint8Array(hashBuffer)));\\n          }).catch(function () {\\n            cb(null, sha256Fallback(unescape(encodeURIComponent(message))));\\n          });\\n          return;\\n        }\\n      } catch (e) {}\\n      cb(null, sha256Fallback(unescape(encodeURIComponent(message))));\\n    }\\n\\n    // =========================\\n    // Deterministic JSON (stable order)\\n    // =========================\\n    function stableStringify(value) {\\n      var t = Object.prototype.toString.call(value);\\n      if (t === \\\"[object Null]\\\" || t === \\\"[object Undefined]\\\") return \\\"null\\\";\\n      if (t === \\\"[object Number]\\\" || t === \\\"[object Boolean]\\\") return JSON.stringify(value);\\n      if (t === \\\"[object String]\\\") return JSON.stringify(String(value));\\n      if (t === \\\"[object Array]\\\") {\\n        var a = [];\\n        for (var i = 0; i < value.length; i++) a.push(stableStringify(value[i]));\\n        return \\\"[\\\" + a.join(\\\",\\\") + \\\"]\\\";\\n      }\\n      if (t === \\\"[object Object]\\\") {\\n        var keys = [];\\n        for (var k in value) if (Object.prototype.hasOwnProperty.call(value, k)) keys.push(k);\\n        keys.sort();\\n        var parts = [];\\n        for (i = 0; i < keys.length; i++) parts.push(JSON.stringify(keys[i]) + \\\":\\\" + stableStringify(value[keys[i]]));\\n        return \\\"{\\\" + parts.join(\\\",\\\") + \\\"}\\\";\\n      }\\n      return JSON.stringify(String(value));\\n    }\\n\\n    // =========================\\n    // Receipt + Prompt Entries\\n    // =========================\\n    function setReceiptText(text) {\\n      state.receiptText = text || \\\"\\\";\\n      receiptPre.textContent = state.receiptText ? state.receiptText : \\\"No receipt minted yet.\\\";\\n      if (state.receiptText) storageSet(\\\"mh8_music_receipt\\\", state.receiptText);\\n      else storageRemove(\\\"mh8_music_receipt\\\");\\n    }\\n\\n    function setPromptEntriesText(text) {\\n      state.promptEntriesText = text || \\\"\\\";\\n      if (state.promptEntriesText) storageSet(\\\"mh8_music_prompt_entries\\\", state.promptEntriesText);\\n      else storageRemove(\\\"mh8_music_prompt_entries\\\");\\n      renderPromptEntries();\\n    }\\n\\n    function renderPromptEntries() {\\n      var t = state.promptEntriesText ? state.promptEntriesText : \\\"No prompt entries completed yet. Mint a receipt first.\\\";\\n      promptEntriesPre.textContent = t;\\n    }\\n\\n    // Layer 1: Lyrics (paste)\\n    function buildLyricsSection(portals) {\\n      var lyrics = String((portals && portals.LYRICS) ? portals.LYRICS : \\\"\\\").trim();\\n      return lyrics;\\n    }\\n\\n    // Layer 2: Style / tags / settings (paste)\\n    function buildStyleSection(platform, portals) {\\n      platform = String(platform || \\\"suno\\\");\\n      var p = portals || {};\\n\\n      function line(lbl, v) {\\n        v = String(v || \\\"\\\").trim();\\n        if (!v) return null;\\n        return \\\"• [\\\" + lbl + \\\"] \\\" + v;\\n      }\\n\\n      var lines = [];\\n      lines.push(\\\"• [PLATFORM] \\\" + (platform === \\\"suno\\\" ? \\\"SUNO\\\" : platform === \\\"udio\\\" ? \\\"UDIO\\\" : \\\"CUSTOM\\\"));\\n\\n      var l;\\n      l = line(\\\"MODEL_BEHAVIOR_CONTROL\\\", p.MODEL_BEHAVIOR_CONTROL); if (l) lines.push(l);\\n\\n      l = line(\\\"GENRE\\\", p.GENRE); if (l) lines.push(l);\\n      l = line(\\\"MOOD\\\", p.MOOD); if (l) lines.push(l);\\n      l = line(\\\"ENERGY\\\", p.ENERGY); if (l) lines.push(l);\\n      l = line(\\\"VOCAL_INTENSITY\\\", p.VOCAL_INTENSITY); if (l) lines.push(l);\\n      l = line(\\\"ERA\\\", p.ERA_INFLUENCE); if (l) lines.push(l);\\n      l = line(\\\"BPM\\\", p.BPM); if (l) lines.push(l);\\n      l = line(\\\"KEY_SIGNATURE\\\", p.KEY_SIGNATURE); if (l) lines.push(l);\\n\\n      l = line(\\\"BACKING_VOX\\\", p.BACKING_VOX); if (l) lines.push(l);\\n      l = line(\\\"DRUMS\\\", p.DRUMS); if (l) lines.push(l);\\n      l = line(\\\"GUITAR\\\", p.GUITAR); if (l) lines.push(l);\\n      l = line(\\\"BASS\\\", p.BASS); if (l) lines.push(l);\\n      l = line(\\\"PIANO_KEYS\\\", p.PIANO_KEYS); if (l) lines.push(l);\\n      l = line(\\\"SYNTH\\\", p.SYNTH); if (l) lines.push(l);\\n      l = line(\\\"PERCUSSION\\\", p.PERCUSSION); if (l) lines.push(l);\\n\\n      l = line(\\\"DYNAMICS\\\", p.DYNAMICS); if (l) lines.push(l);\\n      l = line(\\\"MIX\\\", p.MIX); if (l) lines.push(l);\\n      l = line(\\\"MASTER\\\", p.MASTER); if (l) lines.push(l);\\n\\n      var lock = String(p.CONTINUITY_LOCK || \\\"\\\").trim();\\n      if (lock) lines.push(\\\"• [CONTINUITY_LOCK] \\\" + lock.replace(/\\\\r?\\\\n/g, \\\" | \\\"));\\n\\n      // UPDATE #3: include SHA256 ID portal entry in Layer 2\\n      l = line(\\\"SHA256_ID\\\", p.SHA256_ID); if (l) lines.push(l);\\n\\n      var xh = String(p.EXTRA_HOOKS || \\\"\\\").trim();\\n      if (xh) {\\n        lines.push(\\\"\\\");\\n        lines.push(\\\"• [EXTRA_HOOKS] (sealed)\\\");\\n        var xl = xh.split(/\\\\r?\\\\n/).map(function(s){ return s.trim(); }).filter(Boolean);\\n        for (var i = 0; i < xl.length; i++) lines.push(\\\"  \\\" + xl[i]);\\n      }\\n\\n      var notes = String(p.NOTES || \\\"\\\").trim();\\n      if (notes) {\\n        lines.push(\\\"\\\");\\n        lines.push(\\\"• [NOTES]\\\");\\n        var nl = notes.split(/\\\\r?\\\\n/);\\n        for (i = 0; i < nl.length; i++) lines.push(\\\"  \\\" + String(nl[i]));\\n      }\\n\\n      return lines.join(\\\"\\\\n\\\").trim();\\n    }\\n\\n    // UPDATE #5: include minted SHA256 as LAST bracketed entry in Prompt Entries Completed\\n    function buildPromptEntriesCompleted(layer1, layer2, mintedSha256Hex) {\\n      var out = [];\\n      out.push(\\\"[PROMPT ENTRIES COMPLETED — COPY/PASTE]\\\");\\n      out.push(\\\"INSTRUCTIONS: Copy/paste the two blocks below into your AI music platform. Layer 3 is NOT included here.\\\");\\n      out.push(\\\"\\\");\\n      out.push(\\\"[LAYER 1 — LYRICS (PASTE THIS)]\\\");\\n      out.push(\\\"Paste into the platform’s LYRICS field.\\\");\\n      out.push(\\\"\\\");\\n      out.push(layer1 || \\\"\\\");\\n      out.push(\\\"\\\");\\n      out.push(\\\"[LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)]\\\");\\n      out.push(\\\"Paste into the platform’s STYLE / PROMPT / TAGS field.\\\");\\n      out.push(\\\"\\\");\\n      out.push(layer2 || \\\"\\\");\\n\\n      // REQUIRED: last entry\\n      out.push(\\\"\\\");\\n      out.push(\\\"[SHA 256 ID + \\\" + String(mintedSha256Hex || \\\"\\\").trim() + \\\"]\\\");\\n\\n      return out.join(\\\"\\\\n\\\").replace(/\\\\n{3,}/g, \\\"\\\\n\\\\n\\\");\\n    }\\n\\n    function mintReceipt() {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n\\n      // UPDATE #2: enforce continuity lock before minting (ensures receipts reflect locked values)\\n      applyContinuityLockToPortals(tab);\\n\\n      // UPDATE #3: ensure SHA256_ID exists before minting\\n      if (tab.portals && typeof tab.portals.SHA256_ID === \\\"undefined\\\") tab.portals.SHA256_ID = \\\"\\\";\\n\\n      var platform = String(state.platform);\\n      var ts = nowISO();\\n      var brand = \\\"ACBEATZ.COM\\\";\\n\\n      var portals = tab.portals || makeEmptyPortals();\\n      var layer1 = buildLyricsSection(portals);\\n      var layer2 = buildStyleSection(platform, portals);\\n\\n      var corePayload = {\\n        mh8_system: \\\"MH8-AI-MUSIC-PROOF-OF-CREATION-VAULT\\\",\\n        receipt_type: \\\"MH8-PROMPT-TRIPLE-LAYER\\\",\\n        receipt_version: \\\"2.0.0\\\",\\n        created_utc: ts,\\n        brand: brand,\\n        platform: platform,\\n        prompt_name: tab.name,\\n        layers: {\\n          layer_1_lyrics: layer1,\\n          layer_2_style: layer2\\n        },\\n        portals: portals,\\n\\n        // UPDATE #2: continuity meta is included in receipt payload (additive)\\n        continuity_lock: getTabContinuity(tab),\\n\\n        imported_hooks_count: state.hooks ? state.hooks.length : 0\\n      };\\n\\n      var canonical = stableStringify(corePayload);\\n      var hashInput = \\\"MH8-Acbeatz.com|\\\" + canonical;\\n\\n      mintBtn.disabled = true;\\n      mintBtn.textContent = \\\"Minting...\\\";\\n\\n      sha256Hex(hashInput, function (err, hash) {\\n        mintBtn.disabled = false;\\n        mintBtn.textContent = \\\"Mint Proof Receipt\\\";\\n        if (err) { showToast(\\\"Mint failed. (Hash error)\\\", \\\"MINT\\\"); return; }\\n\\n        var humanPretty =\\n          \\\"MH8-Acbeatz.com — MH8 AI Music Proof-of-Creation Vault\\\\n\\\" +\\n          \\\"Receipt: Prompt (triple-layer)\\\\n\\\" +\\n          \\\"Logic: Lyrics + Style + Hooks cryptographically sealed\\\\n\\\" +\\n          \\\"Timestamp: \\\" + ts + \\\"\\\\n\\\" +\\n          \\\"SHA256: \\\" + hash + \\\"\\\\n\\\" +\\n          \\\"Brand: \\\" + brand + \\\"\\\\n\\\" +\\n          \\\"Crypto receipt: ✅ Complete (local SHA-256)\\\\n\\\" +\\n          \\\"Integrity rule: NON-COPIABLE WHEN HASH-CHAIN BROKEN\\\\n\\\" +\\n          \\\"© 2026 ACBEATZ.COM — All rights reserved.\\\";\\n\\n        var receiptDisplay = [];\\n        receiptDisplay.push(\\\"##==============================\\\");\\n        receiptDisplay.push(\\\"LAYER 1 — LYRICS (PASTE THIS)\\\");\\n        receiptDisplay.push(\\\"INSTRUCTIONS: Paste ONLY this layer into the AI music platform’s LYRICS field.\\\");\\n        receiptDisplay.push(\\\"##==============================\\\");\\n        receiptDisplay.push(\\\"\\\");\\n        receiptDisplay.push(layer1 || \\\"\\\");\\n        receiptDisplay.push(\\\"\\\");\\n        receiptDisplay.push(\\\"##==============================\\\");\\n        receiptDisplay.push(\\\"LAYER 2 — STYLE / TAGS / SETTINGS (PASTE THIS)\\\");\\n        receiptDisplay.push(\\\"INSTRUCTIONS: Paste ONLY this layer into the platform’s STYLE / PROMPT / TAGS field.\\\");\\n        receiptDisplay.push(\\\"NOTE: This includes continuity lock + extra hooks + SHA256 ID if provided.\\\");\\n        receiptDisplay.push(\\\"##==============================\\\");\\n        receiptDisplay.push(\\\"\\\");\\n        receiptDisplay.push(layer2 || \\\"\\\");\\n        receiptDisplay.push(\\\"\\\");\\n        receiptDisplay.push(\\\"=========================================\\\");\\n        receiptDisplay.push(\\\"LAYER 3 — HASHED RECEIPT (DO NOT PASTE)\\\");\\n        receiptDisplay.push(\\\"INSTRUCTIONS: Do NOT paste this into any AI music platform.\\\");\\n        receiptDisplay.push(\\\"Keep it as proof-of-creation for audit/dispute/reference.\\\");\\n        receiptDisplay.push(\\\"=========================================\\\");\\n        receiptDisplay.push(\\\"\\\");\\n\\n        var machineHard = {\\n          mh8_system: \\\"ACBEATZ.COM\\\",\\n          brand: brand,\\n          created_utc: ts,\\n          integrity_rule: \\\"NON-COPIABLE WHEN HASH-CHAIN BROKEN\\\",\\n          core_payload: corePayload,\\n          canonical_payload: canonical,\\n          hash_input: hashInput,\\n          sha256_hex: hash,\\n          cryptographic_receipt_status: \\\"VERIFIED_LOCAL\\\"\\n        };\\n\\n        var finalObj = {\\n          human_pretty: humanPretty,\\n          machine_hard: machineHard,\\n          created_utc: ts,\\n          sha256: hash,\\n          payload: {\\n            platform: platform,\\n            prompt_name: tab.name,\\n            layer_1_lyrics: layer1,\\n            layer_2_style: layer2,\\n            portals: portals,\\n\\n            // UPDATE #2: explicit continuity snapshot in payload mirror (additive)\\n            continuity_lock: getTabContinuity(tab)\\n          },\\n          receipt_text: receiptDisplay.join(\\\"\\\\n\\\")\\n        };\\n\\n        var fullReceipt = finalObj.receipt_text + \\\"\\\\n\\\\n\\\" + JSON.stringify(finalObj, null, 2);\\n        finalObj.full_receipt = fullReceipt;\\n\\n        setReceiptText(fullReceipt);\\n\\n        // UPDATE #5: pass minted SHA256 into Prompt Entries Completed and append as LAST line\\n        var pe = buildPromptEntriesCompleted(layer1, layer2, hash);\\n        setPromptEntriesText(pe);\\n\\n        var vault = getVault();\\n        vault.push(finalObj);\\n        var ok = setVault(vault);\\n        showToast(ok ? \\\"Receipt minted + logged locally.\\\" : \\\"Receipt minted. Log storage blocked (device policy).\\\", \\\"MINT\\\");\\n        if (state.showLog) renderVault();\\n      });\\n    }\\n\\n    // =========================\\n    // Copy helpers\\n    // =========================\\n    function fallbackCopyText(text) {\\n      try {\\n        legacyCopyArea.value = text;\\n        legacyCopyArea.style.display = \\\"block\\\";\\n        legacyCopyArea.select();\\n        legacyCopyArea.setSelectionRange(0, legacyCopyArea.value.length);\\n        var ok = document.execCommand(\\\"copy\\\");\\n        legacyCopyArea.style.display = \\\"none\\\";\\n        return ok;\\n      } catch (e) {\\n        try { legacyCopyArea.style.display = \\\"none\\\"; } catch (e2) {}\\n        return false;\\n      }\\n    }\\n    function copyAnyText(text) {\\n      if (!text) return;\\n      try {\\n        if (navigator.clipboard && window.isSecureContext) {\\n          navigator.clipboard.writeText(text).then(function () {\\n            showToast(\\\"Copied to clipboard.\\\", \\\"COPY\\\");\\n          }).catch(function () {\\n            var ok = fallbackCopyText(text);\\n            showToast(ok ? \\\"Copied (legacy mode).\\\" : \\\"Copy failed (browser restriction).\\\", \\\"COPY\\\");\\n          });\\n          return;\\n        }\\n      } catch (e) {}\\n      var ok2 = fallbackCopyText(text);\\n      showToast(ok2 ? \\\"Copied (legacy mode).\\\" : \\\"Copy failed (browser restriction).\\\", \\\"COPY\\\");\\n    }\\n\\n    function copyReceipt() {\\n      if (!state.receiptText) { showToast(\\\"Nothing to copy. Mint a receipt first.\\\", \\\"COPY\\\"); return; }\\n      copyAnyText(state.receiptText);\\n    }\\n\\n    // =========================\\n    // Export\\n    // =========================\\n    function downloadText(filename, text, mime) {\\n      mime = mime || \\\"application/octet-stream\\\";\\n      try {\\n        var blob = new Blob([text], { type: mime });\\n        var a = document.createElement(\\\"a\\\");\\n        a.href = URL.createObjectURL(blob);\\n        a.download = filename;\\n        document.body.appendChild(a);\\n        a.click();\\n        setTimeout(function () {\\n          URL.revokeObjectURL(a.href);\\n          document.body.removeChild(a);\\n        }, 0);\\n        return true;\\n      } catch (e) {\\n        try {\\n          var a2 = document.createElement(\\\"a\\\");\\n          a2.href = \\\"data:\\\" + mime + \\\";charset=utf-8,\\\" + encodeURIComponent(text);\\n          a2.download = filename;\\n          document.body.appendChild(a2);\\n          a2.click();\\n          document.body.removeChild(a2);\\n          return true;\\n        } catch (e2) { return false; }\\n      }\\n    }\\n\\n    function exportReceipt() {\\n      if (!state.receiptText) { showToast(\\\"Nothing to export. Mint a receipt first.\\\", \\\"EXPORT\\\"); return; }\\n      var ok = downloadText(\\\"mh8-music-receipt.txt\\\", state.receiptText, \\\"text/plain\\\");\\n      showToast(ok ? \\\"Receipt exported.\\\" : \\\"Export blocked by browser.\\\", \\\"EXPORT\\\");\\n    }\\n\\n    function exportVault() {\\n      var vault = getVault();\\n      var text = vault.length ? JSON.stringify(vault, null, 2) : \\\"[]\\\";\\n      var ok = downloadText(\\\"mh8-music-vault.json\\\", text, \\\"application/json\\\");\\n      showToast(ok ? \\\"Vault exported as JSON.\\\" : \\\"Export blocked by browser.\\\", \\\"EXPORT\\\");\\n    }\\n\\n    function copyPromptEntries() {\\n      if (!state.promptEntriesText) { showToast(\\\"Nothing to copy. Mint a receipt first.\\\", \\\"COPY\\\"); return; }\\n      copyAnyText(state.promptEntriesText);\\n    }\\n    function exportPromptEntries() {\\n      if (!state.promptEntriesText) { showToast(\\\"Nothing to export. Mint a receipt first.\\\", \\\"EXPORT\\\"); return; }\\n      var ok = downloadText(\\\"mh8-music-prompt-entries-completed.txt\\\", state.promptEntriesText, \\\"text/plain\\\");\\n      showToast(ok ? \\\"Prompt entries exported.\\\" : \\\"Export blocked by browser.\\\", \\\"EXPORT\\\");\\n    }\\n    function clearPromptEntries() {\\n      setPromptEntriesText(\\\"\\\");\\n      showToast(\\\"Prompt entries cleared.\\\", \\\"PROMPT\\\");\\n    }\\n\\n    // =========================\\n    // Log toggle\\n    // =========================\\n    function setLogVisible(visible) {\\n      state.showLog = !!visible;\\n      if (state.showLog) {\\n        logCard.className = \\\"card logCompact\\\";\\n        logBtn.setAttribute(\\\"aria-expanded\\\", \\\"true\\\");\\n        renderVault();\\n        showToast(\\\"Log opened (compact + scroll).\\\", \\\"LOG\\\");\\n      } else {\\n        logCard.className = \\\"card hidden logCompact\\\";\\n        logBtn.setAttribute(\\\"aria-expanded\\\", \\\"false\\\");\\n        showToast(\\\"Log closed.\\\", \\\"LOG\\\");\\n      }\\n      storageSet(\\\"mh8_music_show_log\\\", state.showLog ? \\\"1\\\" : \\\"0\\\");\\n    }\\n\\n    // =========================\\n    // Clear editor\\n    // =========================\\n    function clearEditor() {\\n      var tab = findTab(state.activeId);\\n      if (!tab) return;\\n      tab.portals = makeEmptyPortals();\\n\\n      // UPDATE #2: clear lock meta as well (still additive behavior; matches \\\"reset\\\")\\n      tab.continuity_lock = { engaged:false, created_utc:\\\"\\\", genre:\\\"\\\", bpm:\\\"\\\", key_signature:\\\"\\\" };\\n      setContinuityLockPortalFromMeta(tab);\\n\\n      // UPDATE #3: ensure SHA256_ID reset present\\n      if (tab.portals && typeof tab.portals.SHA256_ID === \\\"undefined\\\") tab.portals.SHA256_ID = \\\"\\\";\\n\\n      persistTabs();\\n      renderPortals();\\n      showToast(\\\"Editor cleared (all music portals reset).\\\", \\\"CLEAR\\\");\\n    }\\n\\n    // =========================\\n    // Receipt toggle\\n    // =========================\\n    function setReceiptOpen(open) {\\n      state.receiptOpen = !!open;\\n      storageSet(\\\"mh8_music_receipt_open\\\", state.receiptOpen ? \\\"1\\\" : \\\"0\\\");\\n      receiptToggleBtn.setAttribute(\\\"aria-expanded\\\", state.receiptOpen ? \\\"true\\\" : \\\"false\\\");\\n      receiptChev.textContent = state.receiptOpen ? \\\"▾\\\" : \\\"▸\\\";\\n      receiptBody.className = state.receiptOpen ? \\\"cardBody\\\" : \\\"cardBody hidden\\\";\\n    }\\n    function toggleReceiptOpen() {\\n      setReceiptOpen(!state.receiptOpen);\\n      showToast(state.receiptOpen ? \\\"Receipt opened.\\\" : \\\"Receipt closed.\\\", \\\"RECEIPT\\\");\\n    }\\n\\n    // =========================\\n    // Load hooks\\n    // =========================\\n    function loadHooks() {\\n      var rawImports = storageGet(\\\"mh8_music_imported_json_files_v1\\\", \\\"\\\");\\n      if (rawImports) {\\n        try {\\n          var arr = JSON.parse(rawImports);\\n          if (Object.prototype.toString.call(arr) === \\\"[object Array]\\\") {\\n            state.imports = arr;\\n            rebuildMergedHooksFromImports();\\n            return;\\n          }\\n        } catch (e) {}\\n      }\\n\\n      var raw = storageGet(\\\"mh8_music_imported_hooks\\\", \\\"\\\");\\n      if (!raw) return;\\n      try {\\n        var arr2 = JSON.parse(raw);\\n        if (Object.prototype.toString.call(arr2) === \\\"[object Array]\\\") state.hooks = arr2;\\n      } catch (e) {}\\n    }\\n\\n    // =========================\\n    // Init\\n    // =========================\\n    function init() {\\n      var savedPlatform = storageGet(\\\"mh8_music_platform\\\", \\\"suno\\\");\\n      if (savedPlatform !== \\\"suno\\\" && savedPlatform !== \\\"udio\\\" && savedPlatform !== \\\"other\\\") savedPlatform = \\\"suno\\\";\\n      state.platform = savedPlatform;\\n      platformSelect.value = state.platform;\\n\\n      var savedTheme = storageGet(\\\"mh8_music_theme\\\", \\\"dark\\\");\\n      if (savedTheme !== \\\"dark\\\" && savedTheme !== \\\"light\\\") savedTheme = \\\"dark\\\";\\n      applyTheme(savedTheme);\\n\\n      var savedMode = storageGet(\\\"mh8_music_portal_mode\\\", \\\"quick\\\");\\n      if (savedMode !== \\\"quick\\\" && savedMode !== \\\"semi\\\" && savedMode !== \\\"adv\\\") savedMode = \\\"quick\\\";\\n      state.portal_mode = savedMode;\\n\\n      var ro = storageGet(\\\"mh8_music_receipt_open\\\", \\\"1\\\") === \\\"1\\\";\\n      setReceiptOpen(ro);\\n\\n      loadTabs();\\n      renderTabs();\\n\\n      loadHooks();\\n\\n      // UPDATE: initialize Portal Editor pack selector to MERGED\\n      state.modal_hook_pack_mode = \\\"merged\\\";\\n      state.modal_hook_pack_id = \\\"\\\";\\n      renderHookPackMenu();\\n\\n      state.activePortalKey = \\\"MODEL_BEHAVIOR_CONTROL\\\";\\n      renderModeButtons();\\n      normalizeActivePortalForMode();\\n\\n      // UPDATE #2/#3: ensure continuity + SHA256_ID exist and portals are in sync on startup\\n      var tab = findTab(state.activeId);\\n      if (tab) {\\n        getTabContinuity(tab);\\n        setContinuityLockPortalFromMeta(tab);\\n        if (tab.portals && typeof tab.portals.SHA256_ID === \\\"undefined\\\") tab.portals.SHA256_ID = \\\"\\\";\\n        applyContinuityLockToPortals(tab);\\n      }\\n\\n      renderPortals();\\n\\n      var savedReceipt = storageGet(\\\"mh8_music_receipt\\\", \\\"\\\");\\n      if (savedReceipt) setReceiptText(savedReceipt);\\n\\n      var savedPE = storageGet(\\\"mh8_music_prompt_entries\\\", \\\"\\\");\\n      if (savedPE) setPromptEntriesText(savedPE);\\n      else renderPromptEntries();\\n\\n      var show = storageGet(\\\"mh8_music_show_log\\\", \\\"0\\\") === \\\"1\\\";\\n      setLogVisible(show);\\n\\n      showToast(\\\"Ready. Build lyrics + style, stack hooks, mint proof receipts.\\\", \\\"MH8\\\");\\n    }\\n\\n    // =========================\\n    // Events\\n    // =========================\\n    platformSelect.onchange = function () {\\n      state.platform = platformSelect.value;\\n      storageSet(\\\"mh8_music_platform\\\", state.platform);\\n      showToast(\\\"Platform set to \\\" + (state.platform === \\\"suno\\\" ? \\\"Suno\\\" : state.platform === \\\"udio\\\" ? \\\"Udio\\\" : \\\"Custom\\\") + \\\".\\\", \\\"PLATFORM\\\");\\n    };\\n\\n    themeBtn.onclick = function () { toggleTheme(); };\\n    logBtn.onclick = function () { setLogVisible(!state.showLog); };\\n    addTabBtn.onclick = function () { addTab(); };\\n\\n    importBtn.onclick = function () { try { importFile.click(); } catch (e) { showToast(\\\"Import blocked by browser.\\\", \\\"IMPORT\\\"); } };\\n    importMgrBtn.onclick = function () { openImportsManager(); };\\n\\n    importFile.onchange = function () {\\n      var files = importFile.files;\\n      if (!files || !files.length) return;\\n      importFromFile(files[0]);\\n      try { importFile.value = \\\"\\\"; } catch (e) {}\\n    };\\n\\n    importsMergeBtn.onclick = function(){ var merged = rebuildMergedHooksFromImports(); showToast(\\\"Merged hooks rebuilt: \\\" + merged + \\\".\\\", \\\"IMPORTS\\\"); };\\n    importsExportAllBtn.onclick = function(){ exportAllImportsAsStringArray(); };\\n    importsClearAllBtn.onclick = function(){ clearAllImports(); };\\n\\n    modeQuickBtn.onclick = function(){ setPortalMode(\\\"quick\\\", true); };\\n    modeSemiBtn.onclick = function(){ setPortalMode(\\\"semi\\\", true); };\\n    modeAdvBtn.onclick = function(){ setPortalMode(\\\"adv\\\", true); };\\n\\n    portalPrevBtn.onclick = function(){ goPrevPortal(); };\\n    portalNextBtn.onclick = function(){ goNextPortal(); };\\n    portalJump.onchange = function(){ var idx = Number(portalJump.value); setActivePortalByIndex(idx, true); };\\n\\n    // Keyboard arrows for portal flow (when not typing / not in modal)\\n    document.addEventListener(\\\"keydown\\\", function(e){\\n      e = e || window.event;\\n      var k = e.key || e.keyCode;\\n\\n      var tag = (document.activeElement && document.activeElement.tagName) ? String(document.activeElement.tagName).toLowerCase() : \\\"\\\";\\n      var isTyping = (tag === \\\"textarea\\\" || tag === \\\"input\\\" || tag === \\\"select\\\");\\n\\n      if (modalBg.style.display === \\\"flex\\\") return;\\n      if (importsBg.style.display === \\\"flex\\\") return;\\n      if (promptEntriesBg.style.display === \\\"flex\\\") return;\\n      if (isTyping) return;\\n\\n      if (k === \\\"ArrowLeft\\\" || k === 37) { prevent(e); goPrevPortal(); }\\n      else if (k === \\\"ArrowRight\\\" || k === 39) { prevent(e); goNextPortal(); }\\n      else if (k === \\\"Enter\\\" || k === 13) { prevent(e); if (state.activePortalKey) openPortalModal(state.activePortalKey); }\\n    });\\n\\n    mintBtn.onclick = function () { mintReceipt(); };\\n    copyBtn.onclick = function () { copyReceipt(); };\\n    exportBtn.onclick = function () { exportReceipt(); };\\n\\n    clearReceiptBtn.onclick = function () { setReceiptText(\\\"\\\"); showToast(\\\"Receipt cleared (screen only).\\\", \\\"RECEIPT\\\"); };\\n    clearEditorBtn.onclick = function () { clearEditor(); };\\n\\n    refreshLogBtn.onclick = function () { renderVault(); showToast(\\\"Log refreshed.\\\", \\\"LOG\\\"); };\\n    exportVaultBtn.onclick = function () { exportVault(); };\\n    clearVaultBtn.onclick = function () {\\n      var ok = storageRemove(\\\"mh8_music_vault\\\");\\n      renderVault();\\n      showToast(ok ? \\\"Local vault cleared.\\\" : \\\"Vault clear blocked by device policy.\\\", \\\"LOG\\\");\\n    };\\n\\n    receiptToggleBtn.onclick = function () { toggleReceiptOpen(); };\\n\\n    promptEntriesBtn.onclick = function (e) { if (e && e.stopPropagation) e.stopPropagation(); openPromptEntries(); };\\n    promptEntriesCopyBtn.onclick = function(){ copyPromptEntries(); };\\n    promptEntriesExportBtn.onclick = function(){ exportPromptEntries(); };\\n    promptEntriesClearBtn.onclick = function(){ clearPromptEntries(); };\\n\\n    // Boot\\n    init();\\n\\n  })();\\n  </script>\\n</body>\\n</html>\"},\"created_utc\":\"2026-02-17T00:19:38.924Z\",\"mh8_system\":\"MH8-PROTOCOL-HUB\",\"receipt_type\":\"MH8-PROTOCOL-HUB-CORE-MINT\",\"receipt_version\":\"PROTOCOL_HUB_UI_V13\"}",
    "hash_input_stats": {
      "hash_input_bytes": 123021,
      "LF": 0,
      "CRLF": 0,
      "CR": 0,
      "endsWithNewline": "NO",
      "preview_first_80": "ACBEATZ.COM|{\"artifact\":{\"core_entry\":\"[MH8-Music Prompt Pro User Interface sha2",
      "preview_last_80": "eipt_type\":\"MH8-PROTOCOL-HUB-CORE-MINT\",\"receipt_version\":\"PROTOCOL_HUB_UI_V13\"}"
    },
    "sha256_hex": "3c3e31eb12b36f7ebc11da31a314f6725cb344ad92267a757dc870088f22aa06",
    "cryptographic_receipt_status": "VERIFIED_LOCAL",
    "court_audit_dispute_evidence_status": "COMPLETE_FOR_BASIC_DISPUTE",
    "reproducible_hashing_instructions": [
      "1) Rebuild core_payload exactly (same keys/values).",
      "2) Canonicalize core_payload as stableStringify (sorted keys, no whitespace).",
      "3) Build hash_input = brand + '|' + canonical_core_payload.",
      "4) SHA-256 over UTF-8 bytes of hash_input => sha256_hex.",
      "5) Compare computed sha256_hex to this receipt's sha256_hex."
    ],
    "sealing_metadata": {
      "sealed_by": "MH8 Protocol Hub (client-side)",
      "sealing_mode": "SELF_SEALED_LOCAL_SHA256",
      "note": "Keep exported receipt + context artifact for provenance."
    }
  }
}
VERIFIED
VERIFIED — hash matches (receipt.hash_input (dual-layer)). WARNING: human_pretty Brand differs from machine_hard brand
computed sha256
3c3e31eb12b36f7ebc11da31a314f6725cb344ad92267a757dc870088f22aa06
expected sha256
3c3e31eb12b36f7ebc11da31a314f6725cb344ad92267a757dc870088f22aa06
matches?
YES
hash input bytes
123021 bytes
newline stats
CRLF=0 | LF=0 | CR=0 | endsWithNewline=NO
hash input preview
ACBEATZ.COM|{"artifact":{"core_entry":"[MH8-Music Prompt Pro User Interface sha256 Sealed Code]\n[https://zenodo.org/uploads/18665540][https://zenodo.org/record … eipt_type":"MH8-PROTOCOL-HUB-CORE-MINT","receipt_version":"PROTOCOL_HUB_UI_V13"}

